//
// aegis - project change supervisor
// Copyright (C) 2008, 2009, 2011, 2012, 2014 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
validation_version_info::~validation_version_info()
{
}
validation_version_info::validation_version_info()
{
}
validation::pointer
validation_version_info::create()
{
return pointer(new validation_version_info());
}
static nstring
resolve(const nstring &filename, const string_list_ty &search_path)
{
for (size_t j = 0; j < search_path.size(); ++j)
{
struct stat st;
nstring path = nstring(search_path[j]) + "/" + filename;
if (stat(path.c_str(), &st) == 0)
return path;
}
return (nstring(search_path[0]) + "/" + filename);
}
static nstring
safe_readlink(const nstring &path)
{
char buf[2000];
int n = readlink(path.c_str(), buf, sizeof(buf));
if (n < 0)
n = 0;
return nstring(buf, n);
}
static nstring
version_info_unsplit(long c, long r, long a)
{
return nstring::format("%ld:%ld:%ld", c, r, a);
}
static bool
extsym(const nstring &t)
{
if (t.size() != 1)
return false;
unsigned char c = t[0];
return (isupper(c) && c != 'U');
}
static void
grope_shared_library(const nstring &filename,
const string_list_ty &search_path, nstring &version_info,
nstring_list &symbols)
{
version_info = "0:0:0";
symbols.clear();
nstring path = resolve(filename, search_path);
//
// libtool creates a symbolic link
// libname.so -> libname.so.1.2.3
//
{
nstring link = safe_readlink(path);
if (link.empty())
return;
nstring_list parts;
parts.split(link, ".");
size_t idx = 0;
while (idx < parts.size())
{
++idx;
if (parts[idx - 1] == "so")
break;
}
if (idx + 3 == parts.size())
{
long n1 = parts[idx].to_long();
long n2 = parts[idx + 1].to_long();
long n3 = parts[idx + 2].to_long();
//
// Libtool actually hacks this differently for each
// operating system. Sheesh. This code un-hacks the Linux
// jiggery pokery. Worry about other OSs when someone asks.
//
version_info = version_info_unsplit(n1 + n2, n3, n2);
}
}
//
// Run the nm -D command to extract the symbols
// and then read the symbols from the temporary file.
//
os_become_orig();
nstring temp_file(os_edit_filename(0));
nstring command = "nm -D " + path.quote_shell() + " > " +
temp_file.quote_shell();
int mode = OS_EXEC_FLAG_ERROK + OS_EXEC_FLAG_SILENT;
if (os_execute_retcode(command, mode, ".") == 0)
{
FILE *fp = fopen(temp_file.c_str(), "r");
if (fp)
{
for (;;)
{
char line[1000];
if (!fgets(line, sizeof(line), fp))
break;
nstring_list args;
args.split(nstring(line), " ", true);
if (args.size() == 3 && extsym(args[1]))
symbols.push_back(args[2]);
}
fclose(fp);
symbols.sort();
}
}
os_unlink(temp_file);
os_become_undo();
}
static nstring
massage_library_filename(const nstring &fn)
{
nstring_list components;
components.split(fn, "/");
nstring last = components.back();
components.pop_back();
if (components.back() != ".libs")
components.push_back(".libs");
components.push_back(last.trim_extension() + ".so");
return components.unsplit("/");
}
static void
version_info_split(const nstring &s, long &c, long &r, long &a)
{
nstring_list parts;
parts.split(s, ":");
c = parts.size() < 1 ? 0 : parts[0].to_long();
if (c < 0)
c = 0;
r = parts.size() < 2 ? 0 : parts[1].to_long();
if (r < 0)
r = 0;
a = parts.size() < 3 ? 0 : parts[2].to_long();
if (a < 0)
a = 0;
else if (a > r)
a = r;
}
bool
validation_version_info::run(change::pointer cp)
{
if (cp->is_a_branch())
{
// For this to be true, the branch is still being developed.
// When a branch is being integrated this will be false (but
// cp->was_a_branch() will still be true).
return true;
}
nstring library_filename =
cp->pconf_attributes_find("aede-policy:version-info:library");
if (library_filename.empty())
{
nstring name(cp->project_get()->trunk_get()->name_get());
library_filename = "lib" + name + "/lib" + name + ".la";
}
nstring ugly_library_filename = massage_library_filename(library_filename);
nstring version_info =
cp->pconf_attributes_find("aemakegen:version-info");
if (version_info.empty())
version_info = "0:0:0";
//
// we want two versions of the library: the new one, and the ancestor one,
// on the assumption that the ancestor one
// was the one previosuly released.
//
// It is not an error if the libraries do not exist yet.
//
project *ancestor = cp->project_get();
if (cp->is_being_developed() && !ancestor->is_a_trunk())
ancestor = ancestor->parent_get();
string_list_ty old_search_path;
ancestor->search_path_get(&old_search_path, true);
nstring old_version_info;
nstring_list old_symbols;
grope_shared_library
(
ugly_library_filename,
old_search_path,
old_version_info,
old_symbols
);
string_list_ty new_search_path;
cp->search_path_get(&new_search_path, true);
nstring new_version_info;
nstring_list new_symbols;
grope_shared_library
(
ugly_library_filename,
new_search_path,
new_version_info,
new_symbols
);
//
// 1. Start with version information of `0:0:0' for each libtool library.
// This has the form C=current:R=revision:A=age
//
long old_current = 0;
long old_revision = 0;
long old_age = 0;
version_info_split(old_version_info, old_current, old_revision, old_age);
//
// 2. Update the version information only immediately before a public
// release of your software. More frequent updates are
// unnecessary, and only guarantee that the current interface
// number gets larger faster.
//
long test_current = old_current;
long test_revision = old_revision;
long test_age = old_age;
if (!old_symbols.empty())
{
//
// 3. If the library source code has changed at all since the
// last update, then increment REVISION
//
++test_revision;
//
// 4. If any interfaces have been added, removed, or changed
// since the last update, increment CURRENT, and set REVISION
// to 0.
//
if (old_symbols != new_symbols)
{
++test_current;
test_revision = 0;
}
//
// 5. If any interfaces have been added since the last public
// release, then increment AGE.
//
nstring_list additions = new_symbols - old_symbols;
if (!additions.empty())
++test_age;
//
// 6. If any interfaces have been removed since the last public
// release, then set AGE to 0.
//
nstring_list removals = old_symbols - new_symbols;
if (!removals.empty())
test_age = 0;
}
nstring test_version_info =
version_info_unsplit(test_current, test_revision, test_age);
bool result = true;
if (strverscmp(new_version_info.c_str(), test_version_info.c_str()) < 0)
{
sub_context_ty sc;
sc.var_set_string("File_Name", library_filename);
sc.var_set_string("Number1", test_version_info);
sc.var_set_string("Number2", new_version_info);
change_error
(
cp,
&sc,
i18n("$filename: version should be $number1, not $number2")
);
result = false;
}
if (strverscmp(version_info.c_str(), test_version_info.c_str()) < 0)
{
sub_context_ty sc;
sc.var_set_string("File_Name", "aemakegen:version-info");
sc.var_set_string("Number1", test_version_info);
sc.var_set_string("Number2", version_info);
change_error
(
cp,
&sc,
i18n("$filename: version should be $number1, not $number2")
);
result = false;
}
return result;
}
// vim: set ts=8 sw=4 et :