Comment gérer au mieux la création de versions de code open source à partir du code de recherche confidentiel de mon entreprise?

13

Mon entreprise (appelons-les Acme Technology) possède une bibliothèque d'environ un millier de fichiers source provenant à l'origine de son groupe de recherche Acme Labs, incubés dans un groupe de développement pendant quelques années, et plus récemment, fournis à une poignée de clients sous non-divulgation. Acme s'apprête à publier peut-être 75% du code à la communauté open source. Les 25% restants seraient publiés plus tard, mais pour l'instant, soit ils ne sont pas prêts à être utilisés par le client, soit ils contiennent du code lié aux futures innovations dont ils ont besoin pour rester hors de la portée des concurrents.

Le code est actuellement formaté avec #ifdefs qui permet à la même base de code de fonctionner avec les plates-formes de pré-production qui seront disponibles pour les chercheurs universitaires et un éventail beaucoup plus large de clients commerciaux une fois qu'il sera ouvert, tout en étant en même temps disponible pour l'expérimentation et le prototypage et les tests de compatibilité ascendante avec la future plateforme. Garder une base de code unique est considéré comme essentiel pour l'économie (et la santé mentale) de mon groupe qui aurait du mal à maintenir deux copies en parallèle.

Les fichiers de notre base actuelle ressemblent à ceci:

> // Copyright 2012 (C) Acme Technology, All Rights Reserved.
> // Very large, often varied and restrictive copyright license in English and French,
> // sometimes also embedded in make files and shell scripts with varied 
> // comment styles. 
> 
> 
>   ... Usual header stuff...
>
> void initTechnologyLibrary() {
>     nuiInterface(on);
> #ifdef  UNDER_RESEARCH
>     holographicVisualization(on);
> #endif
> }

Et nous aimerions les convertir en quelque chose comme:

> // GPL Copyright (C) Acme Technology Labs 2012, Some rights reserved.
> // Acme appreciates your interest in its technology, please contact [email protected] 
> // for technical support, and www.acme.com/emergingTech for updates and RSS feed.
> 
>   ... Usual header stuff...
>
> void initTechnologyLibrary() {
>     nuiInterface(on);
> }

Existe-t-il un outil, une bibliothèque d'analyse ou un script populaire qui peut remplacer le droit d'auteur et supprimer non seulement #ifdefs, mais des variantes comme #if défini (UNDER_RESEARCH), etc.?

Le code est actuellement dans Git et serait probablement hébergé quelque part qui utilise Git. Existerait-il un moyen de lier les référentiels en toute sécurité afin que nous puissions réintégrer efficacement nos améliorations avec les versions open source? Des conseils sur d'autres pièges sont les bienvenus.

DeveloperDon
la source
13
Cette base de code crie des branches.
Florian Margaine
Un exemple d'utilisation de branches à cette fin serait le bienvenu.
DeveloperDon

Réponses:

6

Il semble que ce ne serait pas trop difficile d'écrire un script pour analyser les préprocesseurs, les comparer à une liste de constantes définies ( UNDER_RESEARCH, FUTURE_DEVELOPMENT, etc.) et, si la directive peut être évaluée à false GIVEN ce qui est défini, tout supprimer jusqu'à au suivant #endif.

En Python, je ferais quelque chose comme,

import os

src_dir = 'src/'
switches = {'UNDER_RESEARCH': True, 'OPEN_SOURCE': False}
new_header = """// GPL Copyright (C) Acme Technology Labs 2012, Some rights reserved.
// Acme appreciates your interest in its technology, please contact [email protected] 
// for technical support, and www.acme.com/emergingTech for updates and RSS feed.
"""

filenames = os.listdir(src_dir)
for fn in filenames:
    contents = open(src_dir+fn, 'r').read().split('\n')
    outfile = open(src_dir+fn+'-open-source', 'w')
    in_header = True
    skipping = False
    for line in contents:
        # remove original header
        if in_header and (line.strip() == "" or line.strip().startswith('//')):
            continue
        elif in_header:
            in_header = False
            outfile.write(new_header)

        # skip between ifdef directives
        if skipping:
            if line.strip() == "#endif":
                skipping = False
            continue
        # check
        if line.strip().startswith("#ifdef"):
            # parse #ifdef (maybe should be more elegant)
            # this assumes a form of "#ifdef SWITCH" and nothing else
            if line.strip().split()[1] in switches.keys():
                skipping = True
                continue

        # checking for other forms of directives is left as an exercise

        # got this far, nothing special - echo the line
        outfile.write(line)
        outfile.write('\n')

Je suis sûr qu'il existe des façons plus élégantes de le faire, mais c'est rapide et sale et semble faire le travail.

WasabiFlux
la source
Ouah merci. Il y a potentiellement beaucoup de logique pour faire un bon filtre et j'apprécie votre exemple. J'espère trouver quelque chose à réutiliser, et ma machine de développement est rapide avec une grande mémoire, donc les performances ne sont pas très importantes pour exécuter des filtres séparés pour le copyright et les définitions, ou pour exécuter le filtre de définition plus d'une fois. Nous avons en fait plusieurs définitions liées à des mots clés qui désignent plusieurs projets futurs et quelques projets passés qui ne seront pas publiés en open source, mais qui sont toujours utilisés en interne et par des clients adoptants précoces.
DeveloperDon
3

Je pensais à passer votre code à travers le préprocesseur pour ne développer que les macros, ne produisant ainsi que la partie intéressante du #ifdefs.

Quelque chose comme ça devrait fonctionner:

gcc -E yourfile.c

Mais:

  • Vous perdrez tous les commentaires. Vous pouvez les utiliser -CCpour (en quelque sorte) les conserver, mais vous devrez alors supprimer l'ancien avis de copyright
  • #includeles s sont également développés, vous vous retrouverez donc avec un gros fichier contenant tout le contenu des fichiers d'en-tête inclus
  • Vous perdrez les macros "standard".

Il existe peut-être un moyen de limiter les macros développées; cependant ma suggestion ici est de diviser les choses, au lieu de faire un traitement (potentiellement dangereux) sur les fichiers (au fait, comment prévoyez-vous de les conserver après? par exemple réintroduire du code de la version opensource dans votre source fermée?).

C'est-à-dire, essayez de mettre le code que vous souhaitez ouvrir dans des bibliothèques externes autant que possible, puis utilisez-les comme vous le feriez avec n'importe quelle autre bibliothèque, en l'intégrant à d'autres bibliothèques de sources fermées "personnalisées".

Cela peut prendre un peu plus de temps au début pour comprendre comment restructurer les choses, mais c'est certainement la bonne façon d'y parvenir.

redShadow
la source
J'avais pensé s'il pouvait y avoir quelque chose qui pourrait être fait avec le préprocesseur pour éliminer sélectivement les blocs que nous ne publierons pas encore. Le code est complexe et nous aurons probablement besoin de plus de commentaires plutôt que de moins, mais votre suggestion vaut certainement la peine d'être incluse dans la liste de remue-méninges. Questions WRT sur la façon dont nous prévoyons de maintenir la source et de déplacer le code en arrière et en avant à la communauté, il faut plus de planification. Introduire du code dans le code propriétaire soulève de bonnes questions.
DeveloperDon
2

J'ai une solution mais cela demandera un peu de travail

pypreprocessor est une bibliothèque qui fournit un préprocesseur de style c pur pour python qui peut également être utilisé comme GPP (General Purpose Pre-Processor) pour d'autres types de code source.

Voici un exemple de base:

from pypreprocessor import pypreprocessor

pypreprocessor.input = 'input_file.c'
pypreprocessor.output = 'output_file.c'
pypreprocessor.removeMeta = True
pypreprocessor.parse()

Le préprocesseur est extrêmement simple. Il passe à travers la source et commente conditionnellement la source en fonction de ce qui est défini.

Les définitions peuvent être définies via les instructions #define dans la source ou en les définissant dans la liste pypreprocessor.defines.

La définition des paramètres d'entrée / sortie vous permet de définir explicitement quels fichiers sont ouverts / fermés afin qu'un seul préprocesseur puisse être configuré pour traiter par lots un grand nombre de fichiers si vous le souhaitez.

En définissant le paramètre removeMeta sur True, le préprocesseur doit extraire automatiquement toutes les instructions du préprocesseur en ne laissant que le code post-traité.

Remarque: Habituellement, cela n'a pas besoin d'être défini explicitement car python a supprimé automatiquement le code commenté lors de la compilation en bytecode.

Je ne vois qu'un seul cas de bord. Parce que vous cherchez à prétraiter la source C, vous souhaiterez peut-être définir les définitions de processeur de manière explicite (c'est-à-dire via pypreprocessor.defines) et lui dire d'ignorer les instructions #define dans la source. Cela devrait l'empêcher de supprimer accidentellement les constantes que vous pouvez utiliser dans le code source de votre projet. Il n'y a actuellement aucun paramètre pour définir cette fonctionnalité mais il serait trivial de l'ajouter.

Voici un exemple trivial:

from pypreprocessor import pypreprocessor

# run the script in 'production' mode
if 'commercial' in sys.argv:
    pypreprocessor.defines.append('commercial')

if 'open' in sys.argv:
    pypreprocessor.defines.append('open')

pypreprocessor.removeMeta = True
pypreprocessor.parse()

Puis la source:

#ifdef commercial
// Copyright 2012 (C) Acme Technology, All Rights Reserved.
// Very large, often varied and restrictive copyright license in English and French,
// sometimes also embedded in make files and shell scripts with varied 
// comment styles.
#ifdef open
// GPL Copyright (C) Acme Technology Labs 2012, Some rights reserved.
// Acme appreciates your interest in its technology, please contact [email protected] 
// for technical support, and www.acme.com/emergingTech for updates and RSS feed.
#endif

Remarque: Évidemment, vous devrez trouver un moyen de définir les fichiers d'entrée / sortie, mais cela ne devrait pas être trop difficile.

Divulgation: je suis l'auteur original de pypreprocessor.


En plus: je l'ai écrit à l'origine comme une solution au problème de maintenance redouté de python 2k / 3x. Mon approche était de faire le développement 2 et 3 dans les mêmes fichiers sources et d'inclure / exclure simplement les différences en utilisant les directives du préprocesseur. Malheureusement, j'ai découvert à la dure qu'il est impossible d'écrire un vrai préprocesseur pur (c'est-à-dire qu'il ne nécessite pas c) en python car le lexer signale les erreurs de syntaxe dans le code incompatible avant que le préprocesseur ait la chance de s'exécuter. Quoi qu'il en soit, il est toujours utile dans un large éventail de circonstances, y compris la vôtre.

Plie d'Evan
la source
Ça bouge. Si rien d'autre, nous pourrions faire un peu comme un diff à trois voies qui a traité les fichiers avec et sans le code que nous voulions exclure, a pris leur diff, puis a supprimé les lignes diffed de l'original.
DeveloperDon
@DeveloperDon Yep, c'est l'idée générale. Il existe plusieurs façons de le gérer, cela dépend de la façon dont vous prévoyez de gérer le cycle de validation-libération. Cette pièce automatise simplement une grande partie du travail qui serait autrement fastidieux et / ou sujet à erreur.
Evan Plaice
1

Ce serait probablement une bonne idée de

1. ajoutez des balises de commentaire comme:

> // *COPYRIGHT-BEGIN-TAG*
> // Copyright 2012 (C) Acme Technology, All Rights Reserved.
> // Very large, often varied and restrictive copyright license in English and French,
> // sometimes also embedded in make files and shell scripts with varied 
> // comment styles. 
> // *COPYRIGHT-ENG-TAG*
>   ... Usual header stuff...
>
> void initTechnologyLibrary() {
>     nuiInterface(on);
> #ifdef  UNDER_RESEARCH
>     holographicVisualization(on);
> #endif
> }

2. Écrire un script pour que le générateur open source passe par tous les fichiers et remplace le texte entre les balises COPYRIGHT-BEGIN-TAG et COPYRIGHT-ENG-TAG

Alex Hashimi
la source
1
Ai-je besoin de la balise begin? Jusqu'à présent, tous nos fichiers source commencent par le copyright sur la première ligne, et nos scripts shell commencent par le copyright sur la deuxième ligne. Il y a beaucoup de fichiers, donc je voudrais faire le plus petit nombre de modifications manuelles possible.
DeveloperDon
Je pense que certains fichiers peuvent utiliser Doxygen pour délimiter leurs noms de fonction, de paramètre et de valeur de retour. Pour ces fichiers qui ne sont pas déjà configurés de cette façon, cela pourrait vraiment être beaucoup d'édition si nous faisions un choix qui allait plus loin dans cette direction.
DeveloperDon
Au moins, vous devez le changer une fois. si votre politique de copyright a changé, vous pouvez la gérer.
Alex Hashimi
1

Je ne vais pas vous montrer un outil pour convertir votre base de code, de nombreuses réponses l'ont déjà fait. Je réponds plutôt à votre commentaire sur la façon de gérer les branches pour cela.

Vous devriez avoir 2 branches:

  • Communauté (appelons la version open source comme celle-ci)
  • Professionnel (appelons la version source fermée comme celle-ci)

Les préprocesseurs ne devraient pas exister. Vous avez deux versions différentes. Et une base de code plus propre dans l'ensemble.

Vous avez peur de conserver deux copies en parallèle? Ne vous inquiétez pas, vous pouvez fusionner!

Si vous apportez des modifications à la branche communautaire, fusionnez-les simplement dans la branche professionnelle. Git gère ça très bien.

De cette façon, vous conservez 2 copies maintenues de votre base de code. Et en publier un pour l'open source est facile comme bonjour.

Florian Margaine
la source