aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2007-08-13 12:34:39 +0000
committerAvatar Ciaran McCreesh <ciaran.mccreesh@googlemail.com> 2007-08-13 12:34:39 +0000
commitb284b422654b2fcbf54b0225a7b3f9c8e0dee1fb (patch)
tree7aa02b2ca1471d4eb0b38c36b507fc89eec312d5
parent2ddf68dc976c4842eb84ead2b9772d8be3a2876f (diff)
downloadpaludis-b284b422654b2fcbf54b0225a7b3f9c8e0dee1fb.tar.gz
paludis-b284b422654b2fcbf54b0225a7b3f9c8e0dee1fb.tar.xz
More QA work
-rw-r--r--paludis/repositories/e/qa/Makefile.am16
-rw-r--r--paludis/repositories/e/qa/extractors.cc366
-rw-r--r--paludis/repositories/e/qa/extractors.conf7
-rw-r--r--paludis/repositories/e/qa/extractors.hh40
-rw-r--r--paludis/repositories/e/qa/extractors_TEST.cc154
-rw-r--r--paludis/repositories/e/qa/qa_checks.cc5
-rw-r--r--paludis/repositories/e/qa/spec_keys.cc11
-rw-r--r--paludis/repositories/e/qa/spec_keys_TEST.cc26
-rw-r--r--paludis/repositories/fake/fake_package_id.cc9
-rw-r--r--paludis/repositories/fake/fake_package_id.hh1
-rw-r--r--src/clients/qualudis/qualudis.cc37
-rw-r--r--src/clients/qualudis/qualudis_command_line.cc5
-rw-r--r--src/clients/qualudis/qualudis_command_line.hh4
13 files changed, 671 insertions, 10 deletions
diff --git a/paludis/repositories/e/qa/Makefile.am b/paludis/repositories/e/qa/Makefile.am
index 06d54c8..4e4b951 100644
--- a/paludis/repositories/e/qa/Makefile.am
+++ b/paludis/repositories/e/qa/Makefile.am
@@ -15,6 +15,7 @@ if ENABLE_QA
paludis_qa_datadir = $(datadir)/paludis/qa/
paludis_qa_data_DATA = \
+ extractors.conf \
spec_keys_pds_blacklist.DEPEND.conf \
spec_keys_pds_blacklist.RDEPEND.conf \
spec_keys_pds_blacklist.PDEPEND.conf \
@@ -24,6 +25,7 @@ paludis_qa_data_DATA = \
paludis_repositories_e_includedir = $(includedir)/paludis-$(PALUDIS_PC_SLOT)/paludis/repositories/e/
paludis_repositories_e_include_HEADERS = \
eapi_supported.hh \
+ extractors.hh \
homepage_key.hh \
spec_keys.hh \
qa_checks.hh \
@@ -37,6 +39,7 @@ lib_LTLIBRARIES = libpaludiserepositoryqa.la
libpaludiserepositoryqa_la_SOURCES = \
qa.cc \
eapi_supported.cc \
+ extractors.cc \
homepage_key.cc \
qa_checks.cc \
qa_checks_group.cc \
@@ -78,6 +81,7 @@ TESTS_ENVIRONMENT = env \
if ENABLE_QA
TESTS = \
+ extractors_TEST \
spec_keys_TEST \
stray_files_TEST
@@ -107,6 +111,18 @@ stray_files_TEST_LDADD = \
$(top_builddir)/paludis/environments/test/libpaludistestenvironment.la \
$(top_builddir)/test/libtest.a
+extractors_TEST_SOURCES = extractors_TEST.cc
+extractors_TEST_LDADD = \
+ libpaludiserepositoryqa.la \
+ test_extras.o \
+ $(top_builddir)/paludis/repositories/e/libpaludiserepository.la \
+ $(top_builddir)/paludis/repositories/fake/libpaludisfakerepository.la \
+ $(top_builddir)/paludis/util/libpaludisutil.la \
+ $(top_builddir)/paludis/util/test_extras.o \
+ $(top_builddir)/paludis/libpaludis.la \
+ $(top_builddir)/paludis/environments/test/libpaludistestenvironment.la \
+ $(top_builddir)/test/libtest.a
+
endif
built-sources : $(BUILT_SOURCES)
diff --git a/paludis/repositories/e/qa/extractors.cc b/paludis/repositories/e/qa/extractors.cc
new file mode 100644
index 0000000..838da81
--- /dev/null
+++ b/paludis/repositories/e/qa/extractors.cc
@@ -0,0 +1,366 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2007 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "extractors.hh"
+#include <paludis/package_id.hh>
+#include <paludis/metadata_key.hh>
+#include <paludis/qa.hh>
+#include <paludis/dep_spec.hh>
+#include <paludis/config_file.hh>
+#include <paludis/util/stringify.hh>
+#include <paludis/util/system.hh>
+#include <paludis/util/visitor-impl.hh>
+#include <paludis/util/fs_entry.hh>
+#include <paludis/util/iterator.hh>
+#include <paludis/util/mutex.hh>
+#include <paludis/util/options.hh>
+#include <paludis/util/log.hh>
+#include <paludis/util/instantiation_policy-impl.hh>
+#include <paludis/util/tr1_functional.hh>
+#include <algorithm>
+#include <map>
+#include <set>
+
+using namespace paludis;
+using namespace paludis::erepository;
+
+namespace
+{
+ struct ExtractorsRequirements :
+ InstantiationPolicy<ExtractorsRequirements, instantiation_method::SingletonTag>
+ {
+ tr1::shared_ptr<const KeyValueConfigFile> file;
+
+ ExtractorsRequirements(const FSEntry & f = FSEntry(getenv_with_default("PALUDIS_QA_DATA_DIR",
+ stringify(FSEntry(DATADIR) / "paludis" / "qa"))) / "extractors.conf")
+ {
+ try
+ {
+ file.reset(new KeyValueConfigFile(f, KeyValueConfigFileOptions()));
+ }
+ catch (const Exception & e)
+ {
+ Log::get_instance()->message(ll_warning, lc_context) << "Got error '" << e.message() << "' (" << e.what()
+ << ") when loading extractors.conf for QA extractors_check";
+ file.reset(new KeyValueConfigFile(std::string(), KeyValueConfigFileOptions()));
+ }
+ }
+ };
+
+ struct FlagExtractor :
+ ConstVisitor<GenericSpecTree>,
+ ConstVisitor<GenericSpecTree>::VisitConstSequence<FlagExtractor, AllDepSpec>,
+ ConstVisitor<GenericSpecTree>::VisitConstSequence<FlagExtractor, AnyDepSpec>
+ {
+ std::map<QualifiedPackageName, std::set<UseFlagName> > relevant;
+ std::set<UseFlagName> current;
+ std::set<QualifiedPackageName> needed_packages;
+
+ void visit_leaf(const URIDepSpec & u)
+ {
+ std::string::size_type p(u.filename().rfind('.'));
+ if (std::string::npos == p)
+ return;
+ std::string extension(u.filename().substr(p + 1));
+ std::string needed(ExtractorsRequirements::get_instance()->file->get(extension));
+ if (! needed.empty())
+ {
+ needed_packages.insert(QualifiedPackageName(needed));
+ relevant[QualifiedPackageName(needed)].insert(current.begin(), current.end());
+ }
+ }
+
+ void visit_leaf(const PackageDepSpec & p)
+ {
+ if (p.package_ptr() && needed_packages.count(*p.package_ptr()))
+ relevant[*p.package_ptr()].insert(current.begin(), current.end());
+ }
+
+ void visit_leaf(const BlockDepSpec &)
+ {
+ }
+
+ void visit_leaf(const LabelsDepSpec<URILabelVisitorTypes> &)
+ {
+ }
+
+ void visit_leaf(const PlainTextDepSpec &)
+ {
+ }
+
+ void visit_sequence(const UseDepSpec & u,
+ GenericSpecTree::ConstSequenceIterator cur,
+ GenericSpecTree::ConstSequenceIterator end)
+ {
+ Save<std::set<UseFlagName> > save_current(&current);
+ current.insert(u.flag());
+ std::for_each(cur, end, accept_visitor(*this));
+ }
+
+ using ConstVisitor<GenericSpecTree>::VisitConstSequence<FlagExtractor, AllDepSpec>::visit_sequence;
+ using ConstVisitor<GenericSpecTree>::VisitConstSequence<FlagExtractor, AnyDepSpec>::visit_sequence;
+ };
+
+ struct Requirements :
+ ConstVisitor<GenericSpecTree>,
+ ConstVisitor<GenericSpecTree>::VisitConstSequence<Requirements, AllDepSpec>
+ {
+ const QualifiedPackageName & name;
+ const std::set<UseFlagName> & relevant;
+ std::map<UseFlagName, bool> current;
+ std::set<std::map<UseFlagName, bool> > requirements;
+
+ Requirements(const QualifiedPackageName & n, const std::set<UseFlagName> & r) :
+ name(n),
+ relevant(r)
+ {
+ }
+
+ void add_requirements()
+ {
+ std::set<std::map<UseFlagName, bool> > new_requirements;
+ new_requirements.insert(current);
+ for (std::set<UseFlagName>::const_iterator r(relevant.begin()), r_end(relevant.end()) ;
+ r != r_end ; ++r)
+ {
+ if (! current.count(*r))
+ {
+ std::set<std::map<UseFlagName, bool> > new_requirements_c;
+ for (std::set<std::map<UseFlagName, bool> >::iterator i(new_requirements.begin()),
+ i_end(new_requirements.end()) ;
+ i != i_end ; ++i)
+ {
+ std::map<UseFlagName, bool> n(*i);
+ n[*r] = true;
+ new_requirements_c.insert(n);
+ n[*r] = false;
+ new_requirements_c.insert(n);
+ }
+ std::swap(new_requirements_c, new_requirements);
+ }
+ }
+ requirements.insert(new_requirements.begin(), new_requirements.end());
+ }
+
+ void visit_leaf(const URIDepSpec & u)
+ {
+ std::string::size_type p(u.filename().rfind('.'));
+ if (std::string::npos == p)
+ return;
+ std::string extension(u.filename().substr(p + 1));
+ std::string needed(ExtractorsRequirements::get_instance()->file->get(extension));
+ if (needed.empty() || QualifiedPackageName(needed) != name)
+ return;
+
+ add_requirements();
+ }
+
+ void visit_leaf(const PackageDepSpec & p)
+ {
+ if ((! p.package_ptr()) || (name != *p.package_ptr()))
+ return;
+
+ add_requirements();
+ }
+
+ void visit_leaf(const LabelsDepSpec<URILabelVisitorTypes> &)
+ {
+ }
+
+ void visit_leaf(const BlockDepSpec &)
+ {
+ }
+
+ void visit_leaf(const PlainTextDepSpec &)
+ {
+ }
+
+ using ConstVisitor<GenericSpecTree>::VisitConstSequence<Requirements, AllDepSpec>::visit_sequence;
+
+ void visit_sequence(const UseDepSpec & u,
+ GenericSpecTree::ConstSequenceIterator cur,
+ GenericSpecTree::ConstSequenceIterator end)
+ {
+ Save<std::map<UseFlagName, bool> > save_current(&current);
+ std::pair<std::map<UseFlagName, bool>::const_iterator, bool> p(current.insert(std::make_pair(u.flag(), !u.inverse())));
+ if (p.second || (p.first->second == !u.inverse()))
+ std::for_each(cur, end, accept_visitor(*this));
+ }
+
+ void visit_sequence(const AnyDepSpec &,
+ GenericSpecTree::ConstSequenceIterator,
+ GenericSpecTree::ConstSequenceIterator)
+ {
+ }
+ };
+}
+
+bool
+paludis::erepository::extractors_check(
+ const FSEntry & entry,
+ QAReporter & reporter,
+ const tr1::shared_ptr<const PackageID> & id,
+ const std::string & name)
+{
+ Context context("When performing check '" + name + "' on ID '" + stringify(*id) + "':");
+
+ if (id->src_uri_key())
+ {
+ try
+ {
+ /* Find all USE flags relevant for our operation. Don't use IUSE,
+ * since it needs to work with USE_EXPAND and it's potentially
+ * O(2^n). */
+ FlagExtractor f;
+ id->src_uri_key()->value()->accept(f);
+ if (id->build_dependencies_key())
+ id->build_dependencies_key()->value()->accept(f);
+
+ for (std::map<QualifiedPackageName, std::set<UseFlagName> >::const_iterator
+ r(f.relevant.begin()), r_end(f.relevant.end()) ;
+ r != r_end ; ++r)
+ {
+ if (r->second.size() > 10)
+ {
+ reporter.message(QAMessage(entry, qaml_maybe, name, "Too many flags to determine whether "
+ "extractor dependency requirement '" + stringify(r->first) + "' is met"));
+ continue;
+ }
+
+ /* Find the set of requirements. */
+ Requirements q(r->first, r->second);
+ id->src_uri_key()->value()->accept(q);
+
+ /* Find the set of met requirements. */
+ Requirements m(r->first, r->second);
+ if (id->build_dependencies_key())
+ id->build_dependencies_key()->value()->accept(m);
+
+ /* Find the set of unmet requirements */
+ std::set<std::map<UseFlagName, bool> > unmet;
+ std::set_difference(q.requirements.begin(), q.requirements.end(),
+ m.requirements.begin(), m.requirements.end(), std::inserter(unmet, unmet.begin()));
+
+ /* Simplify the set of unmet requirements: reduce
+ * { { a => x, b => y }, { a => !x, b => y } } to { { b => y } } */
+ bool changed(true);
+ while (changed)
+ {
+ changed = false;
+ std::set<std::map<UseFlagName, bool> > new_unmet;
+ for (std::set<std::map<UseFlagName, bool> >::const_iterator i(unmet.begin()), i_end(unmet.end()) ;
+ i != i_end ; ++i)
+ {
+ std::map<UseFlagName, bool>::const_iterator j_rem(i->end());
+ for (std::map<UseFlagName, bool>::const_iterator j(i->begin()), j_end(i->end()) ;
+ j != j_end ; ++j)
+ {
+ std::map<UseFlagName, bool> n(*i);
+ n[j->first] = !n[j->first];
+ if (unmet.count(n))
+ {
+ j_rem = j;
+ changed = true;
+ break;
+ }
+ }
+
+ if (j_rem != i->end())
+ {
+ std::map<UseFlagName, bool> n(*i);
+ n.erase(j_rem->first);
+ new_unmet.insert(n);
+ }
+ else
+ new_unmet.insert(*i);
+ }
+ std::swap(unmet, new_unmet);
+ }
+
+ /* Simplify the set of unmet requirements: reduce
+ * { { a => x }, { a => x, b => y } } to { { a => x } } */
+ changed = true;
+ while (changed)
+ {
+ changed = false;
+ for (std::set<std::map<UseFlagName, bool> >::iterator i(unmet.begin()), i_end(unmet.end()) ;
+ i != i_end && ! changed ; ++i)
+ {
+ for (std::set<std::map<UseFlagName, bool> >::iterator j(unmet.begin()), j_end(unmet.end()) ;
+ j != j_end && ! changed ; ++j)
+ {
+ if (i == j)
+ continue;
+
+ std::map<UseFlagName, bool> delta;
+ std::set_difference(i->begin(), i->end(), j->begin(), j->end(), std::inserter(delta, delta.begin()));
+ if (delta.empty())
+ {
+ unmet.erase(j);
+ changed = true;
+ }
+ }
+ }
+ }
+
+ /* Messages */
+ if (! unmet.empty())
+ {
+ std::string cond;
+ if (! unmet.begin()->empty())
+ {
+ cond.append(" for USE \"");
+ bool need_and(false);
+ for (std::set<std::map<UseFlagName, bool> >::iterator j(unmet.begin()), j_end(unmet.end()) ;
+ j != j_end ; ++j)
+ {
+ if (need_and)
+ cond.append("\" and \"");
+ need_and = true;
+
+ bool need_space(false);
+ for (std::map<UseFlagName, bool>::const_iterator i(j->begin()), i_end(j->end()) ;
+ i != i_end ; ++i)
+ {
+ if (need_space)
+ cond.append(" ");
+ if (! i->second)
+ cond.append("!");
+ cond.append(stringify(i->first));
+ need_space = true;
+ }
+ }
+ cond.append("\"");
+ }
+
+ reporter.message(QAMessage(entry, qaml_maybe, name, "Extractor '" + stringify(r->first)
+ + "' may be required as a build dependency" + cond));
+ }
+ }
+ }
+ catch (const Exception & e)
+ {
+ reporter.message(QAMessage(entry, qaml_severe, name, "Caught exception '" + stringify(e.message()) + "' ("
+ + stringify(e.what()) + ") when handling key '" + id->src_uri_key()->raw_name() + "'"));
+ }
+ }
+
+ return true;
+}
+
diff --git a/paludis/repositories/e/qa/extractors.conf b/paludis/repositories/e/qa/extractors.conf
new file mode 100644
index 0000000..292d389
--- /dev/null
+++ b/paludis/repositories/e/qa/extractors.conf
@@ -0,0 +1,7 @@
+zip = app-arch/unzip
+jar = app-arch/unzip
+rar = app-arch/unrar
+LHa = app-arch/lha
+LHA = app-arch/lha
+lha = app-arch/lha
+lzh = app-arch/lha
diff --git a/paludis/repositories/e/qa/extractors.hh b/paludis/repositories/e/qa/extractors.hh
new file mode 100644
index 0000000..e8fafb5
--- /dev/null
+++ b/paludis/repositories/e/qa/extractors.hh
@@ -0,0 +1,40 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2007 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef PALUDIS_GUARD_PALUDIS_PALUDIS_REPOSITORIES_E_QA_EXTRACTORS_HH
+#define PALUDIS_GUARD_PALUDIS_PALUDIS_REPOSITORIES_E_QA_EXTRACTORS_HH 1
+
+#include <paludis/qa-fwd.hh>
+#include <paludis/package_id-fwd.hh>
+#include <paludis/util/attributes.hh>
+#include <paludis/util/fs_entry-fwd.hh>
+
+namespace paludis
+{
+ namespace erepository
+ {
+ bool extractors_check(
+ const FSEntry &,
+ QAReporter &,
+ const tr1::shared_ptr<const PackageID> &,
+ const std::string &) PALUDIS_VISIBLE;
+ }
+}
+
+#endif
diff --git a/paludis/repositories/e/qa/extractors_TEST.cc b/paludis/repositories/e/qa/extractors_TEST.cc
new file mode 100644
index 0000000..de07df3
--- /dev/null
+++ b/paludis/repositories/e/qa/extractors_TEST.cc
@@ -0,0 +1,154 @@
+/* vim: set sw=4 sts=4 et foldmethod=syntax : */
+
+/*
+ * Copyright (c) 2007 Ciaran McCreesh <ciaranm@ciaranm.org>
+ *
+ * This file is part of the Paludis package manager. Paludis is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU General
+ * Public License version 2, as published by the Free Software Foundation.
+ *
+ * Paludis 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <paludis/repositories/e/qa/extractors.hh>
+#include <paludis/repositories/fake/fake_repository.hh>
+#include <paludis/repositories/fake/fake_package_id.hh>
+#include <paludis/environments/test/test_environment.hh>
+#include <paludis/package_database.hh>
+#include <paludis/qa.hh>
+#include <test/test_framework.hh>
+#include <test/test_runner.hh>
+
+using namespace paludis;
+using namespace paludis::erepository;
+using namespace test;
+
+namespace
+{
+ struct TestReporter :
+ QAReporter
+ {
+ unsigned count;
+ std::string last_message;
+
+ TestReporter() :
+ count(0)
+ {
+ }
+
+ void message(const QAMessage & m)
+ {
+ ++count;
+ last_message = m.message;
+ }
+ };
+}
+
+namespace test_cases
+{
+ struct GoodTest : TestCase
+ {
+ GoodTest() : TestCase("good") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ tr1::shared_ptr<FakeRepository> repo(new FakeRepository(&env, RepositoryName("repo")));
+ env.package_database()->add_repository(1, repo);
+ tr1::shared_ptr<FakePackageID> id(repo->add_version("cat", "pkg", "1"));
+ id->build_dependencies_key()->set_from_string("app-arch/unzip");
+ id->src_uri_key()->set_from_string("foo.zip");
+
+ TestReporter r;
+ TEST_CHECK(extractors_check(FSEntry("/var/empty"), r, id, "spec keys"));
+ TEST_CHECK_EQUAL(r.count, 0u);
+ }
+ } test_good;
+
+ struct NoneTest : TestCase
+ {
+ NoneTest() : TestCase("none") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ tr1::shared_ptr<FakeRepository> repo(new FakeRepository(&env, RepositoryName("repo")));
+ env.package_database()->add_repository(1, repo);
+ tr1::shared_ptr<FakePackageID> id(repo->add_version("cat", "pkg", "1"));
+ id->build_dependencies_key()->set_from_string("app-misc/foo");
+ id->src_uri_key()->set_from_string("foo.tar.bz2");
+
+ TestReporter r;
+ TEST_CHECK(extractors_check(FSEntry("/var/empty"), r, id, "spec keys"));
+ TEST_CHECK_EQUAL(r.count, 0u);
+ }
+ } test_none;
+
+ struct ComplicatedGoodTest : TestCase
+ {
+ ComplicatedGoodTest() : TestCase("complicated good") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ tr1::shared_ptr<FakeRepository> repo(new FakeRepository(&env, RepositoryName("repo")));
+ env.package_database()->add_repository(1, repo);
+ tr1::shared_ptr<FakePackageID> id(repo->add_version("cat", "pkg", "1"));
+ id->build_dependencies_key()->set_from_string("oink? ( app-arch/unzip ) !oink? ( bar? ( app-arch/unzip ) foo? ( app-arch/unzip ) )");
+ id->src_uri_key()->set_from_string("foo? ( foo.zip ) bar? ( baz? ( moo.zip ) )");
+
+ TestReporter r;
+ TEST_CHECK(extractors_check(FSEntry("/var/empty"), r, id, "spec keys"));
+ TestMessageSuffix s(r.last_message);
+ TEST_CHECK_EQUAL(r.count, 0u);
+ }
+ } test_complicated_good;
+
+ struct BadTest : TestCase
+ {
+ BadTest() : TestCase("bad") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ tr1::shared_ptr<FakeRepository> repo(new FakeRepository(&env, RepositoryName("repo")));
+ env.package_database()->add_repository(1, repo);
+ tr1::shared_ptr<FakePackageID> id(repo->add_version("cat", "pkg", "1"));
+ id->build_dependencies_key()->set_from_string("app-arch/blah");
+ id->src_uri_key()->set_from_string("foo.zip");
+
+ TestReporter r;
+ TEST_CHECK(extractors_check(FSEntry("/var/empty"), r, id, "spec keys"));
+ TEST_CHECK_EQUAL(r.count, 1u);
+ }
+ } test_bad;
+
+ struct ComplicatedBadTest : TestCase
+ {
+ ComplicatedBadTest() : TestCase("complicated bad") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ tr1::shared_ptr<FakeRepository> repo(new FakeRepository(&env, RepositoryName("repo")));
+ env.package_database()->add_repository(1, repo);
+ tr1::shared_ptr<FakePackageID> id(repo->add_version("cat", "pkg", "1"));
+ id->build_dependencies_key()->set_from_string("foo? ( baz? ( app-arch/unzip ) !baz? ( app-arch/unzip ) ) ");
+ id->src_uri_key()->set_from_string("oink? ( a.zip ) !oink? ( bar? ( a.zip ) )");
+
+ TestReporter r;
+ TEST_CHECK(extractors_check(FSEntry("/var/empty"), r, id, "spec keys"));
+ TEST_CHECK_EQUAL(r.count, 1u);
+ TEST_CHECK_EQUAL(r.last_message, "Extractor 'app-arch/unzip' may be required as a build dependency for USE "
+ "\"bar !foo !oink\" and \"!foo oink\"");
+ }
+ } test_complicated_bad;
+}
+
diff --git a/paludis/repositories/e/qa/qa_checks.cc b/paludis/repositories/e/qa/qa_checks.cc
index 85d0869..e283b25 100644
--- a/paludis/repositories/e/qa/qa_checks.cc
+++ b/paludis/repositories/e/qa/qa_checks.cc
@@ -26,6 +26,7 @@
#include <paludis/repositories/e/qa/short_description_key.hh>
#include <paludis/repositories/e/qa/homepage_key.hh>
#include <paludis/repositories/e/qa/spec_keys.hh>
+#include <paludis/repositories/e/qa/extractors.hh>
using namespace paludis;
using namespace paludis::erepository;
@@ -75,6 +76,10 @@ QAChecks::QAChecks() :
_imp->package_id_checks_group->add_check("spec_keys",
tr1::bind(spec_keys_check, _1, _2, _5, "spec_keys"));
_imp->package_id_checks_group->add_prerequirement("spec_keys", "eapi_supported");
+
+ _imp->package_id_checks_group->add_check("extractors",
+ tr1::bind(extractors_check, _1, _2, _5, "extractors"));
+ _imp->package_id_checks_group->add_prerequirement("extractors", "eapi_supported");
}
QAChecks::~QAChecks()
diff --git a/paludis/repositories/e/qa/spec_keys.cc b/paludis/repositories/e/qa/spec_keys.cc
index ffb18b7..90e5d63 100644
--- a/paludis/repositories/e/qa/spec_keys.cc
+++ b/paludis/repositories/e/qa/spec_keys.cc
@@ -39,6 +39,7 @@
#include <paludis/util/instantiation_policy-impl.hh>
#include <algorithm>
#include <map>
+#include <set>
using namespace paludis;
using namespace paludis::erepository;
@@ -92,6 +93,7 @@ namespace
unsigned level;
bool child_of_any;
+ std::set<UseFlagName> uses;
Checker(
const FSEntry & f,
@@ -140,7 +142,7 @@ namespace
{
}
- void visit_sequence(const UseDepSpec &,
+ void visit_sequence(const UseDepSpec & u,
GenericSpecTree::ConstSequenceIterator cur,
GenericSpecTree::ConstSequenceIterator end)
{
@@ -149,8 +151,15 @@ namespace
"'|| ( )' block with 'use? ( )' child in spec key '"
+ stringify(key.raw_name()) + "'"));
+ if (uses.count(u.flag()))
+ reporter.message(QAMessage(entry, qaml_normal, name,
+ "Recursive use of flag '" + stringify(u.flag()) + "' in spec key '"
+ + stringify(key.raw_name()) + "'"));
+
Save<unsigned> save_level(&level, level + 1);
Save<bool> save_child_of_any(&child_of_any, false);
+ Save<std::set<UseFlagName> > save_uses(&uses, uses);
+ uses.insert(u.flag());
if (cur == end)
reporter.message(QAMessage(entry, qaml_normal, name,
"Empty 'use? ( )' block in spec key '" + stringify(key.raw_name()) + "'"));
diff --git a/paludis/repositories/e/qa/spec_keys_TEST.cc b/paludis/repositories/e/qa/spec_keys_TEST.cc
index 9ede1b5..85619d5 100644
--- a/paludis/repositories/e/qa/spec_keys_TEST.cc
+++ b/paludis/repositories/e/qa/spec_keys_TEST.cc
@@ -178,5 +178,31 @@ namespace test_cases
TEST_CHECK_EQUAL(r.count, 1u);
}
} test_deprecated;
+
+ struct RecursiveUseTest : TestCase
+ {
+ RecursiveUseTest() : TestCase("recursive use") { }
+
+ void run()
+ {
+ TestEnvironment env;
+ tr1::shared_ptr<FakeRepository> repo(new FakeRepository(&env, RepositoryName("repo")));
+ env.package_database()->add_repository(1, repo);
+
+ tr1::shared_ptr<FakePackageID> id1(repo->add_version("cat", "pkg", "1"));
+ id1->build_dependencies_key()->set_from_string("x? ( x? ( cat/pkg ) )");
+
+ TestReporter r1;
+ TEST_CHECK(spec_keys_check(FSEntry("/var/empty"), r1, id1, "spec keys"));
+ TEST_CHECK_EQUAL(r1.count, 1u);
+
+ tr1::shared_ptr<FakePackageID> id2(repo->add_version("cat", "pkg", "2"));
+ id2->build_dependencies_key()->set_from_string("x? ( !x? ( cat/pkg ) )");
+
+ TestReporter r2;
+ TEST_CHECK(spec_keys_check(FSEntry("/var/empty"), r2, id2, "spec keys"));
+ TEST_CHECK_EQUAL(r2.count, 1u);
+ }
+ } test_recursive_use;
}
diff --git a/paludis/repositories/fake/fake_package_id.cc b/paludis/repositories/fake/fake_package_id.cc
index 3eb9341..46d8a2f 100644
--- a/paludis/repositories/fake/fake_package_id.cc
+++ b/paludis/repositories/fake/fake_package_id.cc
@@ -301,6 +301,9 @@ namespace paludis
suggested_dependencies(new FakeMetadataSpecTreeKey<DependencySpecTree>("SDEPEND", "Suggested dependencies",
"", tr1::bind(&erepository::parse_depend, _1,
*erepository::EAPIData::get_instance()->eapi_from_string("0")), mkt_dependencies)),
+ src_uri(new FakeMetadataSpecTreeKey<URISpecTree>("SRC_URI", "Source URIs",
+ "", tr1::bind(&erepository::parse_uri, _1,
+ *erepository::EAPIData::get_instance()->eapi_from_string("0")), mkt_dependencies)),
has_masks(false)
{
}
@@ -491,6 +494,12 @@ FakePackageID::src_uri_key() const
return _imp->src_uri;
}
+const tr1::shared_ptr<FakeMetadataSpecTreeKey<URISpecTree> >
+FakePackageID::src_uri_key()
+{
+ return _imp->src_uri;
+}
+
const tr1::shared_ptr<const MetadataSpecTreeKey<URISpecTree> >
FakePackageID::homepage_key() const
{
diff --git a/paludis/repositories/fake/fake_package_id.hh b/paludis/repositories/fake/fake_package_id.hh
index da6dad4..c2c19b9 100644
--- a/paludis/repositories/fake/fake_package_id.hh
+++ b/paludis/repositories/fake/fake_package_id.hh
@@ -174,6 +174,7 @@ namespace paludis
const tr1::shared_ptr<FakeMetadataSpecTreeKey<DependencySpecTree> > run_dependencies_key();
const tr1::shared_ptr<FakeMetadataSpecTreeKey<DependencySpecTree> > post_dependencies_key();
const tr1::shared_ptr<FakeMetadataSpecTreeKey<DependencySpecTree> > suggested_dependencies_key();
+ const tr1::shared_ptr<FakeMetadataSpecTreeKey<URISpecTree> > src_uri_key();
void set_slot(const SlotName &);
diff --git a/src/clients/qualudis/qualudis.cc b/src/clients/qualudis/qualudis.cc
index 87b8f22..268053f 100644
--- a/src/clients/qualudis/qualudis.cc
+++ b/src/clients/qualudis/qualudis.cc
@@ -57,11 +57,40 @@ namespace
struct QualudisReporter :
QAReporter
{
+ FSEntry previous_entry;
+
+ QualudisReporter() :
+ previous_entry("/NONE")
+ {
+ }
+
void message(const QAMessage & msg)
{
- std::cout << colour(cl_package_name, strip_leading_string(stringify(msg.entry.strip_leading(FSEntry::cwd())), "/"))
- << ": " << msg.name << " [" << colour(cl_error, msg.level) << "] "
- << std::endl << " " << msg.message << std::endl;
+ if (previous_entry != msg.entry)
+ {
+ std::cout << colour(cl_package_name, strip_leading_string(stringify(msg.entry.strip_leading(FSEntry::cwd())), "/"))
+ << ":" << std::endl;
+ previous_entry = msg.entry;
+ }
+
+ std::cout << " " << msg.name << " [";
+ switch (msg.level)
+ {
+ case qaml_maybe:
+ case qaml_debug:
+ std::cout << msg.level;
+ break;
+
+ case qaml_minor:
+ case qaml_normal:
+ case qaml_severe:
+ case last_qaml:
+ std::cout << colour(cl_error, stringify(msg.level));
+ break;
+ }
+
+ std::cout << "]:" << std::endl;
+ std::cout << " " << msg.message << std::endl;
}
};
}
@@ -113,7 +142,7 @@ int main(int argc, char *argv[])
.accept_unstable(false)
.repository_type(no_config_environment::ncer_ebuild)
.master_repository_dir(QualudisCommandLine::get_instance()->a_master_repository_dir.argument())
- .disable_metadata_cache(true)
+ .disable_metadata_cache(! QualudisCommandLine::get_instance()->a_use_repository_cache.specified())
));
if (! env->main_repository()->qa_interface)
diff --git a/src/clients/qualudis/qualudis_command_line.cc b/src/clients/qualudis/qualudis_command_line.cc
index 56847ac..33968a5 100644
--- a/src/clients/qualudis/qualudis_command_line.cc
+++ b/src/clients/qualudis/qualudis_command_line.cc
@@ -30,7 +30,6 @@ QualudisCommandLine::QualudisCommandLine() :
"be specified. If no action is specified, the directories specifed "
"on the command line (or, if none, the current directory) are "
"checked."),
- a_describe(&action_args, "describe", 'd', "Describe checks"),
a_version(&action_args, "version", 'V', "Display program version"),
a_help(&action_args, "help", 'h', "Display program help"),
@@ -54,7 +53,9 @@ QualudisCommandLine::QualudisCommandLine() :
a_write_cache_dir(&configuration_options, "write-cache-dir", '\0',
"Use a subdirectory named for the repository name under the specified directory for repository write cache"),
a_master_repository_dir(&configuration_options, "master-repository-dir", '\0',
- "Use the specified location for the master repository")
+ "Use the specified location for the master repository"),
+ a_use_repository_cache(&configuration_options, "use-repository-cache", '\0',
+ "Use the repository's metadata cache, if available (faster, but may miss certain errors)")
{
add_usage_line("[ options ] [ directories ... ]");
add_environment_variable("QUALUDIS_OPTIONS", "Default command-line options.");
diff --git a/src/clients/qualudis/qualudis_command_line.hh b/src/clients/qualudis/qualudis_command_line.hh
index d1f6b08..2ae0465 100644
--- a/src/clients/qualudis/qualudis_command_line.hh
+++ b/src/clients/qualudis/qualudis_command_line.hh
@@ -37,9 +37,6 @@ class QualudisCommandLine :
/// Action arguments.
paludis::args::ArgsGroup action_args;
- /// --describe
- paludis::args::SwitchArg a_describe;
-
/// --version
paludis::args::SwitchArg a_version;
@@ -71,6 +68,7 @@ class QualudisCommandLine :
paludis::args::StringArg a_write_cache_dir;
paludis::args::StringArg a_master_repository_dir;
+ paludis::args::SwitchArg a_use_repository_cache;
///\}
};