[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: build profile syntax ideas



Hi!

On Mon, 2013-10-21 at 07:31:23 +0200, Guillem Jover wrote:
> On Tue, 2013-09-17 at 12:31:17 +0200, Johannes Schauer wrote:
> > To get this issue moving, I have attached a patch which implements the <>
> > version of the proposal. The patch is based upon one by wookey and pehjota [1]
> > and adds testcases, namespace support and the ability to activate more than one
> > profile at once.
> 
> Sorry, I lost track of this, was meaning to get into this but got pulled
> into something else. I've added it now to my TODO list for stuff do deal
> with before 1.17.2

Ok, here's the reworked patch I've got locally which will be included
in today's upload (after I wake up), and the diff against yours.

I've changed the environment variable and the field contents to be
whitespace separated, the former because it's easier to handle from
makefiles, the latter to match the Architecture field. I've pluralized
their names too.

I've also changed the restrictions logic to match the arch one, because
the one proposed here didn't make sense to me in some cases, the problem
though comes from supporting positive and negative profiles in the
same restriction, while supporting multiple active profiles. In
general I'd advise against mixing positive and negative profiles,
though, because it can produce confusing results.

There's a thing I'm not entirely sold on yet, and will probably rethink
it once I wake up, that's the name of the output field, currently
Build-Profiles, but I'm pondering on Built-For-Profiles for example.

In any case given that this is currently periferal to normal packaging,
and that the syntax is not yet accepted by the Debian infrastructure,
I'm open to further refinements during the next weeks after 1.17.2
hits unstable, if need be.

Thanks,
Guillem
From 6ab09eb989aa9a74985b668eb13bf6d30210a661 Mon Sep 17 00:00:00 2001
From: Guillem Jover <guillem@debian.org>
Date: Wed, 4 Dec 2013 05:56:17 +0100
Subject: [PATCH] Add build profiles support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This adds the basic infrastructure support for a new class of generic
build-time dependency restrictions, and in particular implements the
specific build profiles, which will allow to cull build dependencies
depending on the profile being used. There's several things this can
be used for, like new port bootstrapping, reduced package builds, and
similar. In the future other kinds of restrictions could be added as
the build profiles are namespaced with “profile.”. An example field
could be:

  Build-Depends: exotic-compiler, libneeded-dev, tool-tiny,
   tool-huge (>= 1.0) [linux-any] <!profile.embedded !profile.bootstrap>

or even stuff like:

  Depends: net-tools <profile.network>, plugin-curl <!profile.no-plugins>

The generated binary packages and .changes files will get a new
Build-Profiles field containing the active profiles during the build.

In addition the build profile can be selected using the environment
variable DEB_BUILD_PROFILES, with space separated values, such as:

  DEB_BUILD_PROFILES="embedded bootstrap"

The management and possible registration in the profile namespace is
currently out of scope in dpkg, this should probably be handled by a
distribution specific process.

See draft <http://www.hadrons.org/~guillem/debian/docs/embedded.proposal>.

Closes: #661538

Based-on-patch-by: Johannes Schauer <j.schauer@email.de>
Signed-off-by: Guillem Jover <guillem@debian.org>
---
 debian/changelog                   |  6 +++
 dpkg-deb/build.c                   |  1 +
 man/deb-src-control.5              | 14 ++++--
 man/dpkg-buildpackage.1            | 14 +++++-
 man/dpkg-checkbuilddeps.1          | 15 +++++-
 scripts/Dpkg/BuildProfiles.pm      | 82 +++++++++++++++++++++++++++++++
 scripts/Dpkg/Control/FieldsCore.pm | 11 +++--
 scripts/Dpkg/Deps.pm               | 98 ++++++++++++++++++++++++++++++++++++++
 scripts/Makefile.am                |  1 +
 scripts/dpkg-buildpackage.pl       |  7 +++
 scripts/dpkg-checkbuilddeps.pl     | 18 +++++--
 scripts/dpkg-genchanges.pl         |  3 ++
 scripts/dpkg-gencontrol.pl         | 11 +++--
 scripts/dpkg-shlibdeps.pl          |  2 +-
 scripts/t/400_Dpkg_Deps.t          | 17 ++++++-
 15 files changed, 282 insertions(+), 18 deletions(-)
 create mode 100644 scripts/Dpkg/BuildProfiles.pm

diff --git a/debian/changelog b/debian/changelog
index aba5303..4f70647 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -109,6 +109,12 @@ dpkg (1.17.2) UNRELEASED; urgency=low
     the array in a subsequent package processing. Closes: #726112
   * Do not accept empty field names in dpkg.
   * Do not accept an initial hyphen in field names.
+  * Add build profiles support:
+    - Add support for <!profile.name> build-time restrictions in dependencies.
+    - Add support for DEB_BUILD_PROFILES environment variable.
+    - Add new option -P to dpkg-buildpackage and dpkg-checbuilddeps.
+    - Add new Build-Profiles output field in .deb and .changes files.
+    Based on a patch by Johannes Schauer <j.schauer@email.de>. Closes: #661538
 
   [ Updated programs translations ]
   * German (Sven Joachim).
diff --git a/dpkg-deb/build.c b/dpkg-deb/build.c
index 8e84a9d..0c61258 100644
--- a/dpkg-deb/build.c
+++ b/dpkg-deb/build.c
@@ -339,6 +339,7 @@ check_conffiles(const char *dir)
 }
 
 static const char *arbitrary_fields[] = {
+  "Build-Profiles",
   "Built-Using",
   "Package-Type",
   "Subarchitecture",
diff --git a/man/deb-src-control.5 b/man/deb-src-control.5
index d924985..9b6d2dd 100644
--- a/man/deb-src-control.5
+++ b/man/deb-src-control.5
@@ -17,7 +17,7 @@
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .
-.TH deb\-src\-control 5 "2013-04-02" "Debian Project" "Debian"
+.TH deb\-src\-control 5 "2013-09-17" "Debian Project" "Debian"
 .SH NAME
 deb\-src\-control \- Debian source packages' master control file format
 .
@@ -172,8 +172,9 @@ fields is a list of groups of alternative packages. Each group is a list
 of packages separated by vertical bar (or "pipe") symbols, "|". The
 groups are separated by commas. Commas are to be read as "AND", and pipes
 as "OR", with pipes binding more tightly. Each package name is
-optionally followed by a version number specification in parentheses and an
-architecture specification in square brackets.
+optionally followed by a version number specification in parentheses, an
+architecture specification in square brackets, and a profile specification
+in angle brackets.
 
 The syntax of the
 .BR Build\-Conflicts ,
@@ -183,7 +184,8 @@ and
 fields is a list of comma-separated package names, where the comma is read
 as an "AND". Specifying alternative packages using a "pipe" is not supported.
 Each package name is optionally followed by a version number specification in
-parentheses and an architecture specification in square brackets.
+parentheses, an architecture specification in square brackets, and a profile
+specification in angle brackets.
 
 A version number may start with a ">>", in which case any later version
 will match, and may specify or omit the Debian packaging revision (separated
@@ -195,6 +197,10 @@ A architecture specification consists of one or more architecture names,
 separated by whitespace. Exclamation marks may be prepended to each of the
 names, meaning "NOT".
 
+A profile specification consists of one or more profile names, prefixed
+with the "\fBprofile.\fP" namespace, separated by whitespace. Exclamation
+marks may be prepended to each of the names, meaning "NOT".
+
 Note that dependencies on packages in the
 .B build\-essential
 set can be omitted and that declaring build conflicts against them is
diff --git a/man/dpkg-buildpackage.1 b/man/dpkg-buildpackage.1
index bd54c10..c31c5cd 100644
--- a/man/dpkg-buildpackage.1
+++ b/man/dpkg-buildpackage.1
@@ -19,7 +19,7 @@
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .
-.TH dpkg\-buildpackage 1 "2013-08-31" "Debian Project" "dpkg utilities"
+.TH dpkg\-buildpackage 1 "2013-09-17" "Debian Project" "dpkg utilities"
 .SH NAME
 dpkg\-buildpackage \- build binary or source packages from sources
 .
@@ -131,6 +131,13 @@ Specify the GNU system type we build for. It can be used in place
 of \-a or as a complement to override the default GNU system type
 of the target Debian architecture.
 .TP
+.BR \-P \fIprofile\fP[ , ...]
+Specify the profile(s) we build, as a comma-separated list, without the
+"\fBprofile.\fP" namespace prefix. The default behavior is to build for
+no specific profile. Also adds them (as a space separated list) to the
+\fBDEB_BUILD_PROFILES\fP environment variable which allows, for example,
+\fBdebian/rules\fP files to use this information for conditional builds.
+.TP
 .BI \-j jobs
 Number of jobs allowed to be run simultaneously, equivalent to the
 .BR make (1)
@@ -247,6 +254,11 @@ Show the version and exit.
 .B DEB_SIGN_KEYID
 If set, it will be used to sign the \fB.changes\fP and \fB.dsc\fP files.
 Overridden by the \fB\-k\fP option.
+.TP
+.B DEB_BUILD_PROFILES
+If set, it will be used as the active build profile(s) for the package
+being built. It is a space separated list of profile names, without the
+"\fBprofile.\fP" namespace prefix. Overridden by the \fB\-P\fP option.
 
 .SS Reliance on exported environment flags
 Even if \fBdpkg\-buildpackage\fP exports some variables, \fBdebian/rules\fP
diff --git a/man/dpkg-checkbuilddeps.1 b/man/dpkg-checkbuilddeps.1
index 41862d6..6b41acc 100644
--- a/man/dpkg-checkbuilddeps.1
+++ b/man/dpkg-checkbuilddeps.1
@@ -17,7 +17,7 @@
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .
-.TH dpkg\-checkbuilddeps 1 "2012-05-22" "Debian Project" "dpkg utilities"
+.TH dpkg\-checkbuilddeps 1 "2013-09-17" "Debian Project" "dpkg utilities"
 .SH NAME
 dpkg\-checkbuilddeps \- check build dependencies and conflicts
 .
@@ -61,8 +61,21 @@ Check build dependencies/conflicts assuming that the package described in
 the control file is to be built for the given host architecture instead of
 the architecture of the current system.
 .TP
+.BR "\-P " \fIprofile\fP[ , ...]
+Check build dependencies/conflicts assuming that the package described
+in the control file is to be built for the given build profile(s). The
+argument is a comma-separated list of profile names, without the
+"\fBprofile.\fP" namespace prefix.
+.TP
 .BR \-? ", " \-\-help
 Show the usage message and exit.
 .TP
 .BR \-\-version
 Show the version and exit.
+.
+.SH ENVIRONMENT
+.TP
+.B DEB_BUILD_PROFILES
+If set, it will be used as the active build profile(s) for the package
+being built. It is a space separated list of profile names, without the
+"\fBprofile.\fP" namespace prefix. Overridden by the \fB\-P\fP option.
diff --git a/scripts/Dpkg/BuildProfiles.pm b/scripts/Dpkg/BuildProfiles.pm
new file mode 100644
index 0000000..491cef1
--- /dev/null
+++ b/scripts/Dpkg/BuildProfiles.pm
@@ -0,0 +1,82 @@
+# Copyright © 2013 Guillem Jover <guillem@debian.org>
+#
+# 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 2 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 <http://www.gnu.org/licenses/>.
+
+package Dpkg::BuildProfiles;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+our @EXPORT_OK = qw(get_build_profiles set_build_profiles);
+
+use Exporter qw(import);
+
+use Dpkg::BuildEnv;
+
+my $cache_profiles;
+my @build_profiles;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::BuildProfiles - handle build profiles
+
+=head1 DESCRIPTION
+
+The Dpkg::BuildProfiles module provides functions to handle the build
+profiles.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my @profiles = get_build_profiles()
+
+Get an array with the currently active build profiles, taken from
+the environment variable B<DEB_BUILD_PROFILES>.
+
+=cut
+
+sub get_build_profiles {
+    return @build_profiles if $cache_profiles;
+
+    if (Dpkg::BuildEnv::has('DEB_BUILD_PROFILES')) {
+        @build_profiles = split / /, Dpkg::BuildEnv::get('DEB_BUILD_PROFILES');
+    }
+    $cache_profiles = 1;
+
+    return @build_profiles;
+}
+
+=item set_build_profiles(@profiles)
+
+Set C<@profiles> as the current active build profiles, by setting
+the environment variable B<DEB_BUILD_PROFILES>.
+
+=cut
+
+sub set_build_profiles {
+    my (@profiles) = @_;
+
+    @build_profiles = @profiles;
+    Dpkg::BuildEnv::set('DEB_BUILD_PROFILES', join ' ', @profiles);
+}
+
+=back
+
+=cut
+
+1;
diff --git a/scripts/Dpkg/Control/FieldsCore.pm b/scripts/Dpkg/Control/FieldsCore.pm
index e0124fc..6e90de8 100644
--- a/scripts/Dpkg/Control/FieldsCore.pm
+++ b/scripts/Dpkg/Control/FieldsCore.pm
@@ -111,6 +111,10 @@ our %FIELDS = (
         dependency => 'normal',
         dep_order => 3,
     },
+    'Build-Profiles' => {
+        allowed => ALL_PKG | CTRL_FILE_CHANGES,
+        separator => FIELD_SEP_SPACE,
+    },
     'Built-Using' => {
         allowed => ALL_PKG,
         separator => FIELD_SEP_COMMA,
@@ -337,7 +341,8 @@ my @sum_fields = map { $_ eq 'md5' ? 'MD5sum' : &field_capitalize($_) }
 our %FIELD_ORDER = (
     CTRL_PKG_DEB() => [
         qw(Package Package-Type Source Version Built-Using Kernel-Version
-        Architecture Subarchitecture Installer-Menu-Item Essential Origin Bugs
+        Build-Profiles Architecture Subarchitecture
+        Installer-Menu-Item Essential Origin Bugs
         Maintainer Installed-Size), &field_list_pkg_dep(),
         qw(Section Priority Multi-Arch Homepage Description Tag Task)
     ],
@@ -349,8 +354,8 @@ our %FIELD_ORDER = (
         @checksum_fields, qw(Files)
     ],
     CTRL_FILE_CHANGES() => [
-        qw(Format Date Source Binary Binary-Only Architecture Version
-        Distribution Urgency Maintainer Changed-By Description
+        qw(Format Date Source Binary Binary-Only Build-Profiles Architecture
+        Version Distribution Urgency Maintainer Changed-By Description
         Closes Changes),
         @checksum_fields, qw(Files)
     ],
diff --git a/scripts/Dpkg/Deps.pm b/scripts/Dpkg/Deps.pm
index 356ea83..d703abc 100644
--- a/scripts/Dpkg/Deps.pm
+++ b/scripts/Dpkg/Deps.pm
@@ -53,6 +53,7 @@ our $VERSION = '1.02';
 
 use Dpkg::Version;
 use Dpkg::Arch qw(get_host_arch get_build_arch);
+use Dpkg::BuildProfiles qw(get_build_profiles);
 use Dpkg::ErrorHandling;
 use Dpkg::Gettext;
 
@@ -195,6 +196,30 @@ architecture. This implicitely strips off the architecture restriction
 list so that the resulting dependencies are directly applicable to the
 current architecture.
 
+=item use_profiles (defaults to 1)
+
+Take into account the profile restriction part of the dependencies. Set
+to 0 to completely ignore that information.
+
+=item build_profiles (defaults to no profile)
+
+Define the active build profiles. By default no profile is defined.
+
+=item reduce_profiles (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current build
+profile. This implicitly strips off the profile restriction list so
+that the resulting dependencies are directly applicable to the current
+profiles.
+
+=item reduce_restrictions (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current set of
+restrictions. This implicitly strips off any restriction list so that the
+resulting dependencies are directly applicable to the current restriction.
+This currently implies C<reduce_arch> and C<reduce_profiles>, and overrides
+them if set.
+
 =item union (defaults to 0)
 
 If set to 1, returns a Dpkg::Deps::Union instead of a Dpkg::Deps::AND. Use
@@ -216,9 +241,19 @@ sub deps_parse {
     $options{reduce_arch} = 0 if not exists $options{reduce_arch};
     $options{host_arch} = get_host_arch() if not exists $options{host_arch};
     $options{build_arch} = get_build_arch() if not exists $options{build_arch};
+    $options{use_profiles} = 1 if not exists $options{use_profiles};
+    $options{reduce_profiles} = 0 if not exists $options{reduce_profiles};
+    $options{build_profiles} = [ get_build_profiles() ]
+        if not exists $options{build_profiles};
+    $options{reduce_restrictions} = 0 if not exists $options{reduce_restrictions};
     $options{union} = 0 if not exists $options{union};
     $options{build_dep} = 0 if not exists $options{build_dep};
 
+    if ($options{reduce_restrictions}) {
+        $options{reduce_arch} = 1;
+        $options{reduce_profiles} = 1;
+    }
+
     # Strip trailing/leading spaces
     $dep_line =~ s/^\s+//;
     $dep_line =~ s/\s+$//;
@@ -242,6 +277,11 @@ sub deps_parse {
 		$dep_simple->reduce_arch($options{host_arch});
 		next if not $dep_simple->arch_is_concerned($options{host_arch});
 	    }
+	    $dep_simple->{restrictions} = undef if not $options{use_profiles};
+	    if ($options{reduce_profiles}) {
+		$dep_simple->reduce_profiles($options{build_profiles});
+		next if not $dep_simple->profile_is_concerned($options{build_profiles});
+	    }
 	    push @or_list, $dep_simple;
         }
 	next if not @or_list;
@@ -470,6 +510,7 @@ use Dpkg::Arch qw(debarch_is);
 use Dpkg::Version;
 use Dpkg::ErrorHandling;
 use Dpkg::Gettext;
+use Dpkg::Util qw(:list);
 
 use parent qw(Dpkg::Interface::Storable);
 
@@ -493,6 +534,7 @@ sub reset {
     $self->{version} = undef;
     $self->{arches} = undef;
     $self->{archqual} = undef;
+    $self->{restrictions} = undef;
 }
 
 sub parse {
@@ -522,6 +564,11 @@ sub parse_string {
                 \s* (.*?)                   # don't parse architectures now
                 \s* \]                      # closing bracket
               )?                            # end of optional architecture
+              (?:                           # start of optional restriction
+                \s* <                       # open bracket for restriction
+                \s* (.*?)                   # don't parse restrictions now
+                \s* >                       # closing bracket
+              )?                            # end of optional restriction
               \s*$                          # trailing spaces at end
             }x;
     if (defined($2)) {
@@ -536,6 +583,9 @@ sub parse_string {
     if (defined($5)) {
 	$self->{arches} = [ split(/\s+/, $5) ];
     }
+    if (defined($6)) {
+	$self->{restrictions} = [ map { lc } split /\s+/, $6 ];
+    }
 }
 
 sub output {
@@ -550,6 +600,9 @@ sub output {
     if (defined($self->{arches})) {
 	$res .= ' [' . join(' ', @{$self->{arches}}) . ']';
     }
+    if (defined($self->{restrictions})) {
+	$res .= ' <' . join(' ', @{$self->{restrictions}}) . '>';
+    }
     if (defined($fh)) {
 	print { $fh } $res;
     }
@@ -764,6 +817,51 @@ sub has_arch_restriction {
     }
 }
 
+sub profile_is_concerned {
+    my ($self, $build_profiles) = @_;
+
+    return 0 if not defined $self->{package}; # Empty dep
+    return 1 if not defined $self->{restrictions}; # Dep without restrictions
+
+    my $seen_profile = 0;
+    foreach my $restriction (@{$self->{restrictions}}) {
+        # Determine if this restriction is negated, and within the "profile"
+        # namespace, otherwise it does not concern this check.
+        next if $restriction !~ m/^(!)?profile\.(.*)/;
+
+        my $negated = defined $1 && $1 eq '!';
+        my $profile = $2;
+
+        # Determine if the restriction matches any of the specified profiles.
+        my $found = any { $_ eq $profile } @{$build_profiles};
+
+        if ($negated) {
+            if ($found) {
+                $seen_profile = 0;
+                last;
+            } else {
+                # "!profile.this" includes by default all other profiles
+                # unless they also appear in a "!profile.other".
+                $seen_profile = 1;
+            }
+        } elsif ($found) {
+            $seen_profile = 1;
+            last;
+        }
+    }
+    return $seen_profile;
+}
+
+sub reduce_profiles {
+    my ($self, $build_profiles) = @_;
+
+    if (not $self->profile_is_concerned($build_profiles)) {
+	$self->reset();
+    } else {
+	$self->{restrictions} = undef;
+    }
+}
+
 sub get_evaluation {
     my ($self, $facts) = @_;
     return if not defined $self->{package};
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index f83adff..aeba5b6 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -58,6 +58,7 @@ nobase_dist_perllib_DATA = \
 	Dpkg/BuildEnv.pm \
 	Dpkg/BuildFlags.pm \
 	Dpkg/BuildOptions.pm \
+	Dpkg/BuildProfiles.pm \
 	Dpkg/Changelog.pm \
 	Dpkg/Changelog/Debian.pm \
 	Dpkg/Changelog/Entry.pm \
diff --git a/scripts/dpkg-buildpackage.pl b/scripts/dpkg-buildpackage.pl
index 1271072..38df61e 100755
--- a/scripts/dpkg-buildpackage.pl
+++ b/scripts/dpkg-buildpackage.pl
@@ -31,6 +31,7 @@ use Dpkg ();
 use Dpkg::Gettext;
 use Dpkg::ErrorHandling;
 use Dpkg::BuildOptions;
+use Dpkg::BuildProfiles qw(set_build_profiles);
 use Dpkg::Compression;
 use Dpkg::Version;
 use Dpkg::Changelog::Parse;
@@ -62,6 +63,7 @@ sub usage {
   -tc            clean source tree when finished.
   -D (default)   check build dependencies and conflicts.
   -d             do not check build dependencies and conflicts.
+  -P<profiles>   assume given build profiles as active (comma-separated list).
   -R<rules>      rules file to execute (default is debian/rules).
   -T<target>     call debian/rules <target> with the proper environment.
       --as-root  ensure -T calls the target with root rights.
@@ -123,6 +125,7 @@ my $signchanges = 1;
 my $buildtarget = 'build';
 my $binarytarget = 'binary';
 my $targetarch = my $targetgnusystem = '';
+my @build_profiles = ();
 my $call_target = '';
 my $call_target_as_root = 0;
 my (@checkbuilddep_opts, @changes_opts, @source_opts);
@@ -187,6 +190,8 @@ while (@ARGV) {
 	$usepause = 1;
     } elsif (/^-a(.*)$/) {
 	$targetarch = $1;
+    } elsif (/^-P(.*)$/) {
+	@build_profiles = split /,/, $1;
     } elsif (/^-s[iad]$/) {
 	push @changes_opts, $_;
     } elsif (/^-(?:s[insAkurKUR]|[zZ].*|i.*|I.*)$/) {
@@ -283,6 +288,8 @@ if (defined $parallel) {
     $build_opts->export();
 }
 
+set_build_profiles(@build_profiles) if @build_profiles;
+
 my $cwd = cwd();
 my $dir = basename($cwd);
 
diff --git a/scripts/dpkg-checkbuilddeps.pl b/scripts/dpkg-checkbuilddeps.pl
index 72485e8..cf5ef27 100755
--- a/scripts/dpkg-checkbuilddeps.pl
+++ b/scripts/dpkg-checkbuilddeps.pl
@@ -28,6 +28,7 @@ use Dpkg ();
 use Dpkg::Gettext;
 use Dpkg::ErrorHandling;
 use Dpkg::Arch qw(get_host_arch);
+use Dpkg::BuildProfiles qw(get_build_profiles set_build_profiles);
 use Dpkg::Deps;
 use Dpkg::Control::Info;
 
@@ -51,6 +52,7 @@ sub usage {
   -c build-conf  use given string for build conflicts instead of
                  retrieving them from control file
   -a arch        assume given host architecture
+  -P profiles    assume given build profiles (comma-separated list)
   --admindir=<directory>
                  change the administrative directory.
   -?, --help     show this help message.
@@ -63,6 +65,7 @@ sub usage {
 my $ignore_bd_arch = 0;
 my $ignore_bd_indep = 0;
 my ($bd_value, $bc_value);
+my $bp_value;
 my $host_arch = get_host_arch();
 my $admindir = $Dpkg::ADMINDIR;
 my @options_spec = (
@@ -73,6 +76,7 @@ my @options_spec = (
     'd=s' => \$bd_value,
     'c=s' => \$bc_value,
     'a=s' => \$host_arch,
+    'P=s' => \$bp_value,
     'admindir=s' => \$admindir,
 );
 
@@ -81,6 +85,10 @@ my @options_spec = (
     GetOptions(@options_spec);
 }
 
+# Update currently active build profiles.
+set_build_profiles(split(/,/, $bp_value)) if ($bp_value);
+my @build_profiles = get_build_profiles();
+
 my $controlfile = shift || 'debian/control';
 
 my $control = Dpkg::Control::Info->new($controlfile);
@@ -103,13 +111,15 @@ my (@unmet, @conflicts);
 
 if ($bd_value) {
 	push @unmet, build_depends('Build-Depends/Build-Depends-Arch/Build-Depends-Indep',
-		deps_parse($bd_value, build_dep => 1, host_arch => $host_arch,
-			   reduce_arch => 1), $facts);
+		deps_parse($bd_value, reduce_restrictions => 1, build_dep => 1,
+			   build_profiles => \@build_profiles,
+			   host_arch => $host_arch), $facts);
 }
 if ($bc_value) {
 	push @conflicts, build_conflicts('Build-Conflicts/Build-Conflicts-Arch/Build-Conflicts-Indep',
-		deps_parse($bc_value, build_dep => 1, host_arch => $host_arch,
-			   reduce_arch => 1, union => 1), $facts);
+		deps_parse($bc_value, reduce_restrictions => 1, build_dep => 1,
+			   build_profiles => \@build_profiles, union => 1,
+			   host_arch => $host_arch), $facts);
 }
 
 if (@unmet) {
diff --git a/scripts/dpkg-genchanges.pl b/scripts/dpkg-genchanges.pl
index 808218b..1454a83 100755
--- a/scripts/dpkg-genchanges.pl
+++ b/scripts/dpkg-genchanges.pl
@@ -30,6 +30,7 @@ use Dpkg::Util qw(:list);
 use Dpkg::File;
 use Dpkg::Checksums;
 use Dpkg::ErrorHandling;
+use Dpkg::BuildProfiles qw(get_build_profiles);
 use Dpkg::Arch qw(get_host_arch debarch_eq debarch_is);
 use Dpkg::Compression;
 use Dpkg::Control::Info;
@@ -470,6 +471,8 @@ unshift(@archvalues,'source') unless is_binaryonly;
     unless $include & ARCH_INDEP;
 $fields->{'Architecture'} = join(' ',@archvalues);
 
+$fields->{'Build-Profiles'} = join ' ', get_build_profiles();
+
 $fields->{'Description'} = "\n" . join("\n", sort @descriptions);
 
 $fields->{'Files'} = '';
diff --git a/scripts/dpkg-gencontrol.pl b/scripts/dpkg-gencontrol.pl
index 5d08a39..5332e5e 100755
--- a/scripts/dpkg-gencontrol.pl
+++ b/scripts/dpkg-gencontrol.pl
@@ -30,6 +30,7 @@ use Dpkg::Util qw(:list);
 use Dpkg::File;
 use Dpkg::Arch qw(get_host_arch debarch_eq debarch_is);
 use Dpkg::Package;
+use Dpkg::BuildProfiles qw(get_build_profiles);
 use Dpkg::Deps;
 use Dpkg::Control;
 use Dpkg::Control::Info;
@@ -253,7 +254,7 @@ $facts->add_installed_package($fields->{'Package'}, $fields->{'Version'},
                               $fields->{'Architecture'}, $fields->{'Multi-Arch'});
 if (exists $pkg->{'Provides'}) {
     my $provides = deps_parse($substvars->substvars($pkg->{'Provides'}, no_warn => 1),
-                              reduce_arch => 1, union => 1);
+                              reduce_restrictions => 1, union => 1);
     if (defined $provides) {
 	foreach my $subdep ($provides->get_deps()) {
 	    if ($subdep->isa('Dpkg::Deps::Simple')) {
@@ -275,7 +276,8 @@ foreach my $field (field_list_pkg_dep()) {
 	    msg_prefix => sprintf(_g('%s field of package %s: '), $field, $pkg->{Package}));
 	if (field_get_dep_type($field) eq 'normal') {
 	    $dep = deps_parse($field_value, use_arch => 1,
-			      reduce_arch => $reduce_arch);
+	                      reduce_arch => $reduce_arch,
+	                      reduce_profiles => 1);
 	    error(_g('error occurred while parsing %s field: %s'), $field,
                   $field_value) unless defined $dep;
 	    $dep->simplify_deps($facts, @seen_deps);
@@ -283,7 +285,8 @@ foreach my $field (field_list_pkg_dep()) {
 	    push @seen_deps, $dep;
 	} else {
 	    $dep = deps_parse($field_value, use_arch => 1,
-                              reduce_arch => $reduce_arch, union => 1);
+	                      reduce_arch => $reduce_arch,
+	                      reduce_profiles => 1, union => 1);
 	    error(_g('error occurred while parsing %s field: %s'), $field,
                   $field_value) unless defined $dep;
 	    $dep->simplify_deps($facts);
@@ -297,6 +300,8 @@ foreach my $field (field_list_pkg_dep()) {
     }
 }
 
+$fields->{'Build-Profiles'} = join ' ', get_build_profiles();
+
 for my $f (qw(Package Version)) {
     error(_g('missing information for output field %s'), $f)
         unless defined $fields->{$f};
diff --git a/scripts/dpkg-shlibdeps.pl b/scripts/dpkg-shlibdeps.pl
index 7129dc2..25f5d1e 100755
--- a/scripts/dpkg-shlibdeps.pl
+++ b/scripts/dpkg-shlibdeps.pl
@@ -147,7 +147,7 @@ usageerr(_g('need at least one executable')) unless scalar keys %exec;
 my $control = Dpkg::Control::Info->new();
 my $fields = $control->get_source();
 my $bd_value = deps_concat($fields->{'Build-Depends'}, $fields->{'Build-Depends-Arch'});
-my $build_deps = deps_parse($bd_value, build_dep => 1, reduce_arch => 1);
+my $build_deps = deps_parse($bd_value, build_dep => 1, reduce_restrictions => 1);
 error(_g('error occurred while parsing %s'), 'Build-Depends/Build-Depends-Arch')
     unless defined $build_deps;
 
diff --git a/scripts/t/400_Dpkg_Deps.t b/scripts/t/400_Dpkg_Deps.t
index b37a1c6..bf0750e 100644
--- a/scripts/t/400_Dpkg_Deps.t
+++ b/scripts/t/400_Dpkg_Deps.t
@@ -16,7 +16,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 20;
+use Test::More tests => 24;
 use Dpkg::Arch qw(get_host_arch);
 
 use_ok('Dpkg::Deps');
@@ -58,6 +58,21 @@ is($dep_i386->output(), 'libc6 (>= 2.5)', 'Arch reduce 1/3');
 is($dep_alpha->output(), 'libc6.1', 'Arch reduce 2/3');
 is($dep_hurd->output(), 'libc0.1', 'Arch reduce 3/3');
 
+my $field_profile = 'dep1 <!profile.stage1 !profile.notest>, ' .
+'dep2 <profile.stage1 !profile.notest>, ' .
+'dep3 <!profile.stage1 profile.notest>, ' .
+'dep4 <profile.stage1 profile.notest>, ' .
+'dep5 <profile.stage1>, dep6 <!profile.stage1>';
+my $dep_noprof = deps_parse($field_profile, reduce_profiles => 1, build_profiles => []);
+my $dep_stage1 = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1']);
+my $dep_notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['notest']);
+my $dep_stage1notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1', 'notest']);
+is($dep_noprof->output(), 'dep1, dep2, dep3, dep6', 'Profile reduce 1/4');
+is($dep_stage1->output(), 'dep2, dep4, dep5', 'Profile reduce 2/4');
+is($dep_notest->output(), 'dep3, dep4, dep6', 'Profile reduce 3/4');
+# The result of this test might seems strange, but that's because the first
+# explicit match wins, be it positive or negative.
+is($dep_stage1notest->output(), 'dep2, dep4, dep5', 'Profile reduce 4/4');
 
 my $facts = Dpkg::Deps::KnownFacts->new();
 $facts->add_installed_package('mypackage', '1.3.4-1', get_host_arch(), 'no');
-- 
1.8.5

diff --git a/debian/changelog b/debian/changelog
index aba5303..4f70647 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -109,6 +109,12 @@ dpkg (1.17.2) UNRELEASED; urgency=low
     the array in a subsequent package processing. Closes: #726112
   * Do not accept empty field names in dpkg.
   * Do not accept an initial hyphen in field names.
+  * Add build profiles support:
+    - Add support for <!profile.name> build-time restrictions in dependencies.
+    - Add support for DEB_BUILD_PROFILES environment variable.
+    - Add new option -P to dpkg-buildpackage and dpkg-checbuilddeps.
+    - Add new Build-Profiles output field in .deb and .changes files.
+    Based on a patch by Johannes Schauer <j.schauer@email.de>. Closes: #661538
 
   [ Updated programs translations ]
   * German (Sven Joachim).
diff --git a/dpkg-deb/build.c b/dpkg-deb/build.c
index 84150e2..0c61258 100644
--- a/dpkg-deb/build.c
+++ b/dpkg-deb/build.c
@@ -339,6 +339,7 @@ check_conffiles(const char *dir)
 }
 
 static const char *arbitrary_fields[] = {
+  "Build-Profiles",
   "Built-Using",
   "Package-Type",
   "Subarchitecture",
@@ -346,7 +347,6 @@ static const char *arbitrary_fields[] = {
   "Installer-Menu-Item",
   "Homepage",
   "Tag",
-  "Build-Profile",
   NULL
 };
 
diff --git a/man/deb-src-control.5 b/man/deb-src-control.5
index 93d5d94..9b6d2dd 100644
--- a/man/deb-src-control.5
+++ b/man/deb-src-control.5
@@ -17,7 +17,7 @@
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .
-.TH deb\-src\-control 5 "2013-04-02" "Debian Project" "Debian"
+.TH deb\-src\-control 5 "2013-09-17" "Debian Project" "Debian"
 .SH NAME
 deb\-src\-control \- Debian source packages' master control file format
 .
@@ -197,6 +197,10 @@ A architecture specification consists of one or more architecture names,
 separated by whitespace. Exclamation marks may be prepended to each of the
 names, meaning "NOT".
 
+A profile specification consists of one or more profile names, prefixed
+with the "\fBprofile.\fP" namespace, separated by whitespace. Exclamation
+marks may be prepended to each of the names, meaning "NOT".
+
 Note that dependencies on packages in the
 .B build\-essential
 set can be omitted and that declaring build conflicts against them is
diff --git a/man/dpkg-buildpackage.1 b/man/dpkg-buildpackage.1
index ddd52b2..c31c5cd 100644
--- a/man/dpkg-buildpackage.1
+++ b/man/dpkg-buildpackage.1
@@ -19,7 +19,7 @@
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .
-.TH dpkg\-buildpackage 1 "2013-08-31" "Debian Project" "dpkg utilities"
+.TH dpkg\-buildpackage 1 "2013-09-17" "Debian Project" "dpkg utilities"
 .SH NAME
 dpkg\-buildpackage \- build binary or source packages from sources
 .
@@ -131,10 +131,12 @@ Specify the GNU system type we build for. It can be used in place
 of \-a or as a complement to override the default GNU system type
 of the target Debian architecture.
 .TP
-.BI \-P profiles
-Specify the profile(s) we build. The default behavior is to build for no
-specific profile. If more than one profile is required, they are given as a
-comma separated list.
+.BR \-P \fIprofile\fP[ , ...]
+Specify the profile(s) we build, as a comma-separated list, without the
+"\fBprofile.\fP" namespace prefix. The default behavior is to build for
+no specific profile. Also adds them (as a space separated list) to the
+\fBDEB_BUILD_PROFILES\fP environment variable which allows, for example,
+\fBdebian/rules\fP files to use this information for conditional builds.
 .TP
 .BI \-j jobs
 Number of jobs allowed to be run simultaneously, equivalent to the
@@ -252,6 +254,11 @@ Show the version and exit.
 .B DEB_SIGN_KEYID
 If set, it will be used to sign the \fB.changes\fP and \fB.dsc\fP files.
 Overridden by the \fB\-k\fP option.
+.TP
+.B DEB_BUILD_PROFILES
+If set, it will be used as the active build profile(s) for the package
+being built. It is a space separated list of profile names, without the
+"\fBprofile.\fP" namespace prefix. Overridden by the \fB\-P\fP option.
 
 .SS Reliance on exported environment flags
 Even if \fBdpkg\-buildpackage\fP exports some variables, \fBdebian/rules\fP
@@ -261,12 +268,6 @@ respective interface to retrieve the needed values.
 \fBdpkg\-architecture\fP is called with the \fB\-a\fP and \fB\-t\fP
 parameters forwarded. Any variable that is output by its \fB\-s\fP
 option is integrated in the build environment.
-.TP
-.B DEB_BUILD_PROFILE
-Sets the build profile(s) for which the package is to be built. For more than
-one activated build profile, this variable contains a comma separated list of
-build profile names. If the \fB\-P\fP option is given, it takes precedence over 
-the value of this environment variable.
 .
 .SH NOTES
 .SS Compiler flags are no longer exported
diff --git a/man/dpkg-checkbuilddeps.1 b/man/dpkg-checkbuilddeps.1
index 865eb38..6b41acc 100644
--- a/man/dpkg-checkbuilddeps.1
+++ b/man/dpkg-checkbuilddeps.1
@@ -17,7 +17,7 @@
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
 .
-.TH dpkg\-checkbuilddeps 1 "2012-07-14" "Debian Project" "dpkg utilities"
+.TH dpkg\-checkbuilddeps 1 "2013-09-17" "Debian Project" "dpkg utilities"
 .SH NAME
 dpkg\-checkbuilddeps \- check build dependencies and conflicts
 .
@@ -61,11 +61,11 @@ Check build dependencies/conflicts assuming that the package described in
 the control file is to be built for the given host architecture instead of
 the architecture of the current system.
 .TP
-.BI "\-P " profiles
-Check build dependencies/conflicts assuming that the package described in
-the control file is to be built for the given build profile(s). If more than
-one profile is required, they are given as a comma separeted list or by
-specifying the argument multiple times.
+.BR "\-P " \fIprofile\fP[ , ...]
+Check build dependencies/conflicts assuming that the package described
+in the control file is to be built for the given build profile(s). The
+argument is a comma-separated list of profile names, without the
+"\fBprofile.\fP" namespace prefix.
 .TP
 .BR \-? ", " \-\-help
 Show the usage message and exit.
@@ -75,8 +75,7 @@ Show the version and exit.
 .
 .SH ENVIRONMENT
 .TP
-.B DEB_BUILD_PROFILE
-Sets the build profile(s) for which the package is to be built. For more than
-one activated build profile, this variable contains a comma separated list of
-build profile names. If the \fB\-P\fP option is given, it takes precedence over
-the value of this environment variable.
+.B DEB_BUILD_PROFILES
+If set, it will be used as the active build profile(s) for the package
+being built. It is a space separated list of profile names, without the
+"\fBprofile.\fP" namespace prefix. Overridden by the \fB\-P\fP option.
diff --git a/scripts/Dpkg/BuildProfiles.pm b/scripts/Dpkg/BuildProfiles.pm
new file mode 100644
index 0000000..491cef1
--- /dev/null
+++ b/scripts/Dpkg/BuildProfiles.pm
@@ -0,0 +1,82 @@
+# Copyright © 2013 Guillem Jover <guillem@debian.org>
+#
+# 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 2 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 <http://www.gnu.org/licenses/>.
+
+package Dpkg::BuildProfiles;
+
+use strict;
+use warnings;
+
+our $VERSION = '0.01';
+our @EXPORT_OK = qw(get_build_profiles set_build_profiles);
+
+use Exporter qw(import);
+
+use Dpkg::BuildEnv;
+
+my $cache_profiles;
+my @build_profiles;
+
+=encoding utf8
+
+=head1 NAME
+
+Dpkg::BuildProfiles - handle build profiles
+
+=head1 DESCRIPTION
+
+The Dpkg::BuildProfiles module provides functions to handle the build
+profiles.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item my @profiles = get_build_profiles()
+
+Get an array with the currently active build profiles, taken from
+the environment variable B<DEB_BUILD_PROFILES>.
+
+=cut
+
+sub get_build_profiles {
+    return @build_profiles if $cache_profiles;
+
+    if (Dpkg::BuildEnv::has('DEB_BUILD_PROFILES')) {
+        @build_profiles = split / /, Dpkg::BuildEnv::get('DEB_BUILD_PROFILES');
+    }
+    $cache_profiles = 1;
+
+    return @build_profiles;
+}
+
+=item set_build_profiles(@profiles)
+
+Set C<@profiles> as the current active build profiles, by setting
+the environment variable B<DEB_BUILD_PROFILES>.
+
+=cut
+
+sub set_build_profiles {
+    my (@profiles) = @_;
+
+    @build_profiles = @profiles;
+    Dpkg::BuildEnv::set('DEB_BUILD_PROFILES', join ' ', @profiles);
+}
+
+=back
+
+=cut
+
+1;
diff --git a/scripts/Dpkg/Control/FieldsCore.pm b/scripts/Dpkg/Control/FieldsCore.pm
index c21fb2a..6e90de8 100644
--- a/scripts/Dpkg/Control/FieldsCore.pm
+++ b/scripts/Dpkg/Control/FieldsCore.pm
@@ -111,8 +111,9 @@ our %FIELDS = (
         dependency => 'normal',
         dep_order => 3,
     },
-    'Build-Profile' => {
-        allowed => ALL_PKG,
+    'Build-Profiles' => {
+        allowed => ALL_PKG | CTRL_FILE_CHANGES,
+        separator => FIELD_SEP_SPACE,
     },
     'Built-Using' => {
         allowed => ALL_PKG,
@@ -340,7 +341,8 @@ my @sum_fields = map { $_ eq 'md5' ? 'MD5sum' : &field_capitalize($_) }
 our %FIELD_ORDER = (
     CTRL_PKG_DEB() => [
         qw(Package Package-Type Source Version Built-Using Kernel-Version
-        Architecture Subarchitecture Installer-Menu-Item Essential Origin Bugs
+        Build-Profiles Architecture Subarchitecture
+        Installer-Menu-Item Essential Origin Bugs
         Maintainer Installed-Size), &field_list_pkg_dep(),
         qw(Section Priority Multi-Arch Homepage Description Tag Task)
     ],
@@ -352,8 +354,8 @@ our %FIELD_ORDER = (
         @checksum_fields, qw(Files)
     ],
     CTRL_FILE_CHANGES() => [
-        qw(Format Date Source Binary Binary-Only Architecture Version
-        Distribution Urgency Maintainer Changed-By Description
+        qw(Format Date Source Binary Binary-Only Build-Profiles Architecture
+        Version Distribution Urgency Maintainer Changed-By Description
         Closes Changes),
         @checksum_fields, qw(Files)
     ],
diff --git a/scripts/Dpkg/Deps.pm b/scripts/Dpkg/Deps.pm
index 4670104..d703abc 100644
--- a/scripts/Dpkg/Deps.pm
+++ b/scripts/Dpkg/Deps.pm
@@ -53,9 +53,9 @@ our $VERSION = '1.02';
 
 use Dpkg::Version;
 use Dpkg::Arch qw(get_host_arch get_build_arch);
+use Dpkg::BuildProfiles qw(get_build_profiles);
 use Dpkg::ErrorHandling;
 use Dpkg::Gettext;
-use Dpkg::Util qw(:list);
 
 use Exporter qw(import);
 our @EXPORT = qw(deps_concat deps_parse deps_eval_implication deps_compare);
@@ -198,20 +198,28 @@ current architecture.
 
 =item use_profiles (defaults to 1)
 
-Take into account the profile restriction part of the dependencies.  Set
+Take into account the profile restriction part of the dependencies. Set
 to 0 to completely ignore that information.
 
 =item build_profiles (defaults to no profile)
 
-Define the activated build profiles. By default no profile is defined.
+Define the active build profiles. By default no profile is defined.
 
 =item reduce_profiles (defaults to 0)
 
 If set to 1, ignore dependencies that do not concern the current build
-profile. This implicitly strips off the profile restriction list so that
-the resulting dependencies are directly applicable to the current
+profile. This implicitly strips off the profile restriction list so
+that the resulting dependencies are directly applicable to the current
 profiles.
 
+=item reduce_restrictions (defaults to 0)
+
+If set to 1, ignore dependencies that do not concern the current set of
+restrictions. This implicitly strips off any restriction list so that the
+resulting dependencies are directly applicable to the current restriction.
+This currently implies C<reduce_arch> and C<reduce_profiles>, and overrides
+them if set.
+
 =item union (defaults to 0)
 
 If set to 1, returns a Dpkg::Deps::Union instead of a Dpkg::Deps::AND. Use
@@ -235,10 +243,17 @@ sub deps_parse {
     $options{build_arch} = get_build_arch() if not exists $options{build_arch};
     $options{use_profiles} = 1 if not exists $options{use_profiles};
     $options{reduce_profiles} = 0 if not exists $options{reduce_profiles};
-    $options{build_profiles} = [] if not exists $options{build_profiles};
+    $options{build_profiles} = [ get_build_profiles() ]
+        if not exists $options{build_profiles};
+    $options{reduce_restrictions} = 0 if not exists $options{reduce_restrictions};
     $options{union} = 0 if not exists $options{union};
     $options{build_dep} = 0 if not exists $options{build_dep};
 
+    if ($options{reduce_restrictions}) {
+        $options{reduce_arch} = 1;
+        $options{reduce_profiles} = 1;
+    }
+
     # Strip trailing/leading spaces
     $dep_line =~ s/^\s+//;
     $dep_line =~ s/\s+$//;
@@ -262,8 +277,8 @@ sub deps_parse {
 		$dep_simple->reduce_arch($options{host_arch});
 		next if not $dep_simple->arch_is_concerned($options{host_arch});
 	    }
-	    $dep_simple->{profiles} = undef if not $options{use_profiles};
-            if ($options{reduce_profiles}) {
+	    $dep_simple->{restrictions} = undef if not $options{use_profiles};
+	    if ($options{reduce_profiles}) {
 		$dep_simple->reduce_profiles($options{build_profiles});
 		next if not $dep_simple->profile_is_concerned($options{build_profiles});
 	    }
@@ -495,6 +510,7 @@ use Dpkg::Arch qw(debarch_is);
 use Dpkg::Version;
 use Dpkg::ErrorHandling;
 use Dpkg::Gettext;
+use Dpkg::Util qw(:list);
 
 use parent qw(Dpkg::Interface::Storable);
 
@@ -517,8 +533,8 @@ sub reset {
     $self->{relation} = undef;
     $self->{version} = undef;
     $self->{arches} = undef;
-    $self->{profiles} = undef;
     $self->{archqual} = undef;
+    $self->{restrictions} = undef;
 }
 
 sub parse {
@@ -568,7 +584,7 @@ sub parse_string {
 	$self->{arches} = [ split(/\s+/, $5) ];
     }
     if (defined($6)) {
-	$self->{restrictions} = [ map {lc} split(/\s+/, $6) ];
+	$self->{restrictions} = [ map { lc } split /\s+/, $6 ];
     }
 }
 
@@ -585,7 +601,7 @@ sub output {
 	$res .= ' [' . join(' ', @{$self->{arches}}) . ']';
     }
     if (defined($self->{restrictions})) {
-	$res .= " <" . join(" ", @{$self->{restrictions}}) . ">";
+	$res .= ' <' . join(' ', @{$self->{restrictions}}) . '>';
     }
     if (defined($fh)) {
 	print { $fh } $res;
@@ -804,33 +820,45 @@ sub has_arch_restriction {
 sub profile_is_concerned {
     my ($self, $build_profiles) = @_;
 
-    return 0 if not defined $self->{package};  # Empty dep
+    return 0 if not defined $self->{package}; # Empty dep
     return 1 if not defined $self->{restrictions}; # Dep without restrictions
 
     my $seen_profile = 0;
-    # copy restrictions into @tmp so that $restrictions can be modified
-    foreach my $restriction (my @tmp = @{$self->{restrictions}}) {
-        # determine if this restriction is negated
-        my $negated = $restriction =~ s/^!//;
-
-        # restriction doesnt concern this check if not starting with "profile."
-        next if ($restriction !~ s/^profile\.//);
-
-        # determine if the restriction matches any of the specified profiles
-        my $found = Dpkg::Util::any { $_ eq $restriction } @$build_profiles;
-
-        # OR the result with $seen_profile, taking negation into account
-        $seen_profile |= $negated ^ $found;
+    foreach my $restriction (@{$self->{restrictions}}) {
+        # Determine if this restriction is negated, and within the "profile"
+        # namespace, otherwise it does not concern this check.
+        next if $restriction !~ m/^(!)?profile\.(.*)/;
+
+        my $negated = defined $1 && $1 eq '!';
+        my $profile = $2;
+
+        # Determine if the restriction matches any of the specified profiles.
+        my $found = any { $_ eq $profile } @{$build_profiles};
+
+        if ($negated) {
+            if ($found) {
+                $seen_profile = 0;
+                last;
+            } else {
+                # "!profile.this" includes by default all other profiles
+                # unless they also appear in a "!profile.other".
+                $seen_profile = 1;
+            }
+        } elsif ($found) {
+            $seen_profile = 1;
+            last;
+        }
     }
     return $seen_profile;
 }
 
 sub reduce_profiles {
-    my ($self, @build_profiles) = @_;
-    if (not $self->profile_is_concerned(@build_profiles)) {
+    my ($self, $build_profiles) = @_;
+
+    if (not $self->profile_is_concerned($build_profiles)) {
 	$self->reset();
     } else {
-	$self->{profiles} = undef;
+	$self->{restrictions} = undef;
     }
 }
 
diff --git a/scripts/Makefile.am b/scripts/Makefile.am
index f83adff..aeba5b6 100644
--- a/scripts/Makefile.am
+++ b/scripts/Makefile.am
@@ -58,6 +58,7 @@ nobase_dist_perllib_DATA = \
 	Dpkg/BuildEnv.pm \
 	Dpkg/BuildFlags.pm \
 	Dpkg/BuildOptions.pm \
+	Dpkg/BuildProfiles.pm \
 	Dpkg/Changelog.pm \
 	Dpkg/Changelog/Debian.pm \
 	Dpkg/Changelog/Entry.pm \
diff --git a/scripts/dpkg-buildpackage.pl b/scripts/dpkg-buildpackage.pl
index 5ff3916..38df61e 100755
--- a/scripts/dpkg-buildpackage.pl
+++ b/scripts/dpkg-buildpackage.pl
@@ -31,6 +31,7 @@ use Dpkg ();
 use Dpkg::Gettext;
 use Dpkg::ErrorHandling;
 use Dpkg::BuildOptions;
+use Dpkg::BuildProfiles qw(set_build_profiles);
 use Dpkg::Compression;
 use Dpkg::Version;
 use Dpkg::Changelog::Parse;
@@ -62,6 +63,7 @@ sub usage {
   -tc            clean source tree when finished.
   -D (default)   check build dependencies and conflicts.
   -d             do not check build dependencies and conflicts.
+  -P<profiles>   assume given build profiles as active (comma-separated list).
   -R<rules>      rules file to execute (default is debian/rules).
   -T<target>     call debian/rules <target> with the proper environment.
       --as-root  ensure -T calls the target with root rights.
@@ -85,9 +87,6 @@ sub usage {
   -a<arch>       Debian architecture we build for.
   -t<system>     set GNU system type.')
     . "\n\n" . _g(
-'Options passed to dpkg-checkbuilddeps:
-  -P<profiles>   Comma separated list of build profiles.')
-    . "\n\n" . _g(
 'Options passed to dpkg-genchanges:
   -si (default)  source includes orig if new upstream.
   -sa            uploaded source always includes orig.
@@ -192,7 +191,7 @@ while (@ARGV) {
     } elsif (/^-a(.*)$/) {
 	$targetarch = $1;
     } elsif (/^-P(.*)$/) {
-	@build_profiles = split(',',$1);
+	@build_profiles = split /,/, $1;
     } elsif (/^-s[iad]$/) {
 	push @changes_opts, $_;
     } elsif (/^-(?:s[insAkurKUR]|[zZ].*|i.*|I.*)$/) {
@@ -289,6 +288,8 @@ if (defined $parallel) {
     $build_opts->export();
 }
 
+set_build_profiles(@build_profiles) if @build_profiles;
+
 my $cwd = cwd();
 my $dir = basename($cwd);
 
@@ -350,10 +351,6 @@ if (not $signcommand) {
     $signchanges = 0;
 }
 
-unless (defined $ENV{'DEB_BUILD_PROFILE'}) {
-    $ENV{'DEB_BUILD_PROFILE'} = join(',', @build_profiles);
-}
-
 # Preparation of environment stops here
 
 (my $sversion = $version) =~ s/^\d+://;
diff --git a/scripts/dpkg-checkbuilddeps.pl b/scripts/dpkg-checkbuilddeps.pl
index 654b8f2..cf5ef27 100755
--- a/scripts/dpkg-checkbuilddeps.pl
+++ b/scripts/dpkg-checkbuilddeps.pl
@@ -28,9 +28,9 @@ use Dpkg ();
 use Dpkg::Gettext;
 use Dpkg::ErrorHandling;
 use Dpkg::Arch qw(get_host_arch);
+use Dpkg::BuildProfiles qw(get_build_profiles set_build_profiles);
 use Dpkg::Deps;
 use Dpkg::Control::Info;
-use Dpkg::BuildEnv;
 
 textdomain('dpkg-dev');
 
@@ -52,7 +52,7 @@ sub usage {
   -c build-conf  use given string for build conflicts instead of
                  retrieving them from control file
   -a arch        assume given host architecture
-  -P profiles    assume given build profiles (comma separated list)
+  -P profiles    assume given build profiles (comma-separated list)
   --admindir=<directory>
                  change the administrative directory.
   -?, --help     show this help message.
@@ -65,8 +65,8 @@ sub usage {
 my $ignore_bd_arch = 0;
 my $ignore_bd_indep = 0;
 my ($bd_value, $bc_value);
+my $bp_value;
 my $host_arch = get_host_arch();
-my @build_profiles = split(',', Dpkg::BuildEnv::get('DEB_BUILD_PROFILE'));
 my $admindir = $Dpkg::ADMINDIR;
 my @options_spec = (
     'help|?' => sub { usage(); exit(0); },
@@ -76,7 +76,7 @@ my @options_spec = (
     'd=s' => \$bd_value,
     'c=s' => \$bc_value,
     'a=s' => \$host_arch,
-    'P=s' => \@build_profiles,
+    'P=s' => \$bp_value,
     'admindir=s' => \$admindir,
 );
 
@@ -85,8 +85,9 @@ my @options_spec = (
     GetOptions(@options_spec);
 }
 
-# support multiple -P arguments as well as comma separated lists
-@build_profiles = split(',',join(',',@build_profiles));
+# Update currently active build profiles.
+set_build_profiles(split(/,/, $bp_value)) if ($bp_value);
+my @build_profiles = get_build_profiles();
 
 my $controlfile = shift || 'debian/control';
 
@@ -110,15 +111,15 @@ my (@unmet, @conflicts);
 
 if ($bd_value) {
 	push @unmet, build_depends('Build-Depends/Build-Depends-Arch/Build-Depends-Indep',
-		deps_parse($bd_value, build_dep => 1, host_arch => $host_arch,
-			   reduce_arch => 1, build_profiles => $build_profiles,
-			   reduce_profiles => 1), $facts);
+		deps_parse($bd_value, reduce_restrictions => 1, build_dep => 1,
+			   build_profiles => \@build_profiles,
+			   host_arch => $host_arch), $facts);
 }
 if ($bc_value) {
 	push @conflicts, build_conflicts('Build-Conflicts/Build-Conflicts-Arch/Build-Conflicts-Indep',
-		deps_parse($bc_value, build_dep => 1, host_arch => $host_arch,
-			   reduce_arch => 1, build_profiles => $build_profiles,
-			   reduce_profiles => 1, union => 1), $facts);
+		deps_parse($bc_value, reduce_restrictions => 1, build_dep => 1,
+			   build_profiles => \@build_profiles, union => 1,
+			   host_arch => $host_arch), $facts);
 }
 
 if (@unmet) {
diff --git a/scripts/dpkg-genchanges.pl b/scripts/dpkg-genchanges.pl
index 808218b..1454a83 100755
--- a/scripts/dpkg-genchanges.pl
+++ b/scripts/dpkg-genchanges.pl
@@ -30,6 +30,7 @@ use Dpkg::Util qw(:list);
 use Dpkg::File;
 use Dpkg::Checksums;
 use Dpkg::ErrorHandling;
+use Dpkg::BuildProfiles qw(get_build_profiles);
 use Dpkg::Arch qw(get_host_arch debarch_eq debarch_is);
 use Dpkg::Compression;
 use Dpkg::Control::Info;
@@ -470,6 +471,8 @@ unshift(@archvalues,'source') unless is_binaryonly;
     unless $include & ARCH_INDEP;
 $fields->{'Architecture'} = join(' ',@archvalues);
 
+$fields->{'Build-Profiles'} = join ' ', get_build_profiles();
+
 $fields->{'Description'} = "\n" . join("\n", sort @descriptions);
 
 $fields->{'Files'} = '';
diff --git a/scripts/dpkg-gencontrol.pl b/scripts/dpkg-gencontrol.pl
index 2f093b9..5332e5e 100755
--- a/scripts/dpkg-gencontrol.pl
+++ b/scripts/dpkg-gencontrol.pl
@@ -30,6 +30,7 @@ use Dpkg::Util qw(:list);
 use Dpkg::File;
 use Dpkg::Arch qw(get_host_arch debarch_eq debarch_is);
 use Dpkg::Package;
+use Dpkg::BuildProfiles qw(get_build_profiles);
 use Dpkg::Deps;
 use Dpkg::Control;
 use Dpkg::Control::Info;
@@ -37,7 +38,6 @@ use Dpkg::Control::Fields;
 use Dpkg::Substvars;
 use Dpkg::Vars;
 use Dpkg::Changelog::Parse;
-use Dpkg::BuildEnv;
 
 textdomain('dpkg-dev');
 
@@ -254,7 +254,7 @@ $facts->add_installed_package($fields->{'Package'}, $fields->{'Version'},
                               $fields->{'Architecture'}, $fields->{'Multi-Arch'});
 if (exists $pkg->{'Provides'}) {
     my $provides = deps_parse($substvars->substvars($pkg->{'Provides'}, no_warn => 1),
-                              reduce_arch => 1, union => 1);
+                              reduce_restrictions => 1, union => 1);
     if (defined $provides) {
 	foreach my $subdep ($provides->get_deps()) {
 	    if ($subdep->isa('Dpkg::Deps::Simple')) {
@@ -276,7 +276,8 @@ foreach my $field (field_list_pkg_dep()) {
 	    msg_prefix => sprintf(_g('%s field of package %s: '), $field, $pkg->{Package}));
 	if (field_get_dep_type($field) eq 'normal') {
 	    $dep = deps_parse($field_value, use_arch => 1,
-			      reduce_arch => $reduce_arch);
+	                      reduce_arch => $reduce_arch,
+	                      reduce_profiles => 1);
 	    error(_g('error occurred while parsing %s field: %s'), $field,
                   $field_value) unless defined $dep;
 	    $dep->simplify_deps($facts, @seen_deps);
@@ -284,7 +285,8 @@ foreach my $field (field_list_pkg_dep()) {
 	    push @seen_deps, $dep;
 	} else {
 	    $dep = deps_parse($field_value, use_arch => 1,
-                              reduce_arch => $reduce_arch, union => 1);
+	                      reduce_arch => $reduce_arch,
+	                      reduce_profiles => 1, union => 1);
 	    error(_g('error occurred while parsing %s field: %s'), $field,
                   $field_value) unless defined $dep;
 	    $dep->simplify_deps($facts);
@@ -298,8 +300,7 @@ foreach my $field (field_list_pkg_dep()) {
     }
 }
 
-my $build_profiles = Dpkg::BuildEnv::get('DEB_BUILD_PROFILE');
-$fields->{'Build-Profile'} = $build_profiles unless !$build_profiles;
+$fields->{'Build-Profiles'} = join ' ', get_build_profiles();
 
 for my $f (qw(Package Version)) {
     error(_g('missing information for output field %s'), $f)
diff --git a/scripts/dpkg-shlibdeps.pl b/scripts/dpkg-shlibdeps.pl
index 7129dc2..25f5d1e 100755
--- a/scripts/dpkg-shlibdeps.pl
+++ b/scripts/dpkg-shlibdeps.pl
@@ -147,7 +147,7 @@ usageerr(_g('need at least one executable')) unless scalar keys %exec;
 my $control = Dpkg::Control::Info->new();
 my $fields = $control->get_source();
 my $bd_value = deps_concat($fields->{'Build-Depends'}, $fields->{'Build-Depends-Arch'});
-my $build_deps = deps_parse($bd_value, build_dep => 1, reduce_arch => 1);
+my $build_deps = deps_parse($bd_value, build_dep => 1, reduce_restrictions => 1);
 error(_g('error occurred while parsing %s'), 'Build-Depends/Build-Depends-Arch')
     unless defined $build_deps;
 
diff --git a/scripts/t/400_Dpkg_Deps.t b/scripts/t/400_Dpkg_Deps.t
index 85fb534..bf0750e 100644
--- a/scripts/t/400_Dpkg_Deps.t
+++ b/scripts/t/400_Dpkg_Deps.t
@@ -58,17 +58,21 @@ is($dep_i386->output(), 'libc6 (>= 2.5)', 'Arch reduce 1/3');
 is($dep_alpha->output(), 'libc6.1', 'Arch reduce 2/3');
 is($dep_hurd->output(), 'libc0.1', 'Arch reduce 3/3');
 
-my $field_profile = 'dep1 <!profile.stage1 !profile.notest>, dep2 <profile.stage1 !profile.notest>, '.
-'dep3 <!profile.stage1 profile.notest>, dep4 <profile.stage1 profile.notest>, '.
+my $field_profile = 'dep1 <!profile.stage1 !profile.notest>, ' .
+'dep2 <profile.stage1 !profile.notest>, ' .
+'dep3 <!profile.stage1 profile.notest>, ' .
+'dep4 <profile.stage1 profile.notest>, ' .
 'dep5 <profile.stage1>, dep6 <!profile.stage1>';
 my $dep_noprof = deps_parse($field_profile, reduce_profiles => 1, build_profiles => []);
 my $dep_stage1 = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1']);
 my $dep_notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['notest']);
-my $dep_stage1notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1','notest']);
-is($dep_noprof->output(), 'dep1 <!profile.stage1 !profile.notest>, dep2 <profile.stage1 !profile.notest>, dep3 <!profile.stage1 profile.notest>, dep6 <!profile.stage1>', 'Profile reduce 1/4');
-is($dep_stage1->output(), 'dep1 <!profile.stage1 !profile.notest>, dep2 <profile.stage1 !profile.notest>, dep4 <profile.stage1 profile.notest>, dep5 <profile.stage1>', 'Profile reduce 2/4');
-is($dep_notest->output(), 'dep1 <!profile.stage1 !profile.notest>, dep3 <!profile.stage1 profile.notest>, dep4 <profile.stage1 profile.notest>, dep6 <!profile.stage1>', 'Profile reduce 3/4');
-is($dep_stage1notest->output(), 'dep2 <profile.stage1 !profile.notest>, dep3 <!profile.stage1 profile.notest>, dep4 <profile.stage1 profile.notest>, dep5 <profile.stage1>', 'Profile reduce 4/4');
+my $dep_stage1notest = deps_parse($field_profile, reduce_profiles => 1, build_profiles => ['stage1', 'notest']);
+is($dep_noprof->output(), 'dep1, dep2, dep3, dep6', 'Profile reduce 1/4');
+is($dep_stage1->output(), 'dep2, dep4, dep5', 'Profile reduce 2/4');
+is($dep_notest->output(), 'dep3, dep4, dep6', 'Profile reduce 3/4');
+# The result of this test might seems strange, but that's because the first
+# explicit match wins, be it positive or negative.
+is($dep_stage1notest->output(), 'dep2, dep4, dep5', 'Profile reduce 4/4');
 
 my $facts = Dpkg::Deps::KnownFacts->new();
 $facts->add_installed_package('mypackage', '1.3.4-1', get_host_arch(), 'no');

Reply to: