Como posso gerenciar melhor a liberação de código-fonte aberto do código de pesquisa confidencial da minha empresa?

13

Minha empresa (vamos chamá-los de Tecnologia Acme) possui uma biblioteca de aproximadamente mil arquivos de origem originários de seu grupo de pesquisa Acme Labs, incubados em um grupo de desenvolvimento por alguns anos e, mais recentemente, foram fornecidos a alguns clientes sob não divulgação. A Acme está se preparando para liberar talvez 75% do código para a comunidade de código aberto. Os outros 25% seriam lançados mais tarde, mas, por enquanto, não estão prontos para uso do cliente ou contêm código relacionado a inovações futuras que precisam manter fora das mãos dos concorrentes.

Atualmente, o código está formatado com #ifdefs, que permite que a mesma base de código funcione com as plataformas de pré-produção que estarão disponíveis para pesquisadores universitários e para uma gama muito maior de clientes comerciais, uma vez que eles acessam o código aberto e, ao mesmo tempo, disponível para experimentação e prototipagem e teste de compatibilidade com a futura plataforma. Manter uma única base de código é considerado essencial para a economia (e a sanidade) do meu grupo, que teria dificuldade em manter duas cópias em paralelo.

Os arquivos em nossa base atual são mais ou menos assim:

> // 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
> }

E gostaríamos de convertê-los para algo como:

> // 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 uma ferramenta, biblioteca de análise ou script popular que possa substituir os direitos autorais e remover não apenas #ifdefs, mas variações como #if definido (UNDER_RESEARCH) etc.?

Atualmente, o código está no Git e provavelmente seria hospedado em algum lugar que use o Git. Haveria uma maneira de vincular repositórios com segurança para que possamos reintegrar com eficiência nossas melhorias com as versões de código aberto? Conselhos sobre outras armadilhas são bem-vindos.

DesenvolvedorDon
fonte
13
Esta base de código está gritando por ramos.
Florian Margaine
Um exemplo de uso de ramificações para esse fim seria bem-vindo.
DeveloperDon

Respostas:

6

Parece que ele não seria muito difícil escrever um script para analisar os pré-processadores, compará-los com uma lista de constantes definidas ( UNDER_RESEARCH, FUTURE_DEVELOPMENT, etc.) e, se a directiva pode ser avaliada como falsa, dado o que está definido, remover tudo para cima para o próximo #endif.

No Python, eu faria algo como,

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')

Tenho certeza de que existem maneiras mais elegantes de fazer isso, mas isso é rápido e sujo e parece fazer o trabalho.

WasabiFlux
fonte
Uau, obrigada. Existe muita lógica para criar um bom filtro e agradeço o seu exemplo. Espero encontrar algo para reutilização, e minha máquina de desenvolvimento é rápida com uma grande memória, para que o desempenho não seja uma grande preocupação em executar filtros separados para os direitos autorais e define, ou executar o filtro define mais de uma vez. Na verdade, temos várias definições relacionadas a palavras-chave que designam vários projetos futuros e alguns projetos anteriores que não serão lançados em código aberto, mas ainda são usados ​​internamente e pela adoção antecipada de clientes.
DeveloperDon
3

Eu estava pensando em passar seu código através do pré-processador para expandir apenas macros, produzindo apenas a parte interessante no #ifdefs.

Algo assim deve funcionar:

gcc -E yourfile.c

Mas:

  • Você perderá todos os comentários. Você pode usá -CC-los para (meio que) preservá-los, mas ainda precisará retirar o aviso de direitos autorais antigo
  • #includes também são expandidos, então você terá um arquivo grande contendo todo o conteúdo dos arquivos de cabeçalho incluídos
  • Você perderá macros "padrão".

Pode haver uma maneira de limitar quais macros são expandidas; no entanto, minha sugestão aqui é dividir as coisas, em vez de processar (potencialmente perigoso) os arquivos (a propósito, como você planeja mantê-los depois? por exemplo, reintroduzir o código da versão de código-fonte aberto em seu código-fonte fechado?).

Ou seja, tente colocar o código em que você deseja abrir o código-fonte em bibliotecas externas o máximo possível, depois usá-lo como faria com qualquer outra biblioteca, integrando-se a outras bibliotecas de código fechado "personalizadas".

Pode demorar um pouco mais no começo para descobrir como reestruturar as coisas, mas é definitivamente o caminho certo para fazer isso.

redShadow
fonte
Eu havia pensado se poderia haver algo que poderia ser feito com o pré-processador para eliminar seletivamente os blocos que ainda não lançaremos. O código é complexo e provavelmente precisaremos de mais comentários em vez de menos, mas certamente vale a pena sugerir sua sugestão na lista de ideias. Perguntas do WRT sobre como planejamos manter a fonte e mover o código para trás e para a frente para a comunidade, é necessário mais planejamento. A inserção de código no código proprietário levanta algumas boas perguntas.
DeveloperDon
2

Eu tenho uma solução, mas vai exigir um pouco de trabalho

pypreprocessor é uma biblioteca que fornece um pré-processador puro de estilo c para python, que também pode ser usado como GPP (General Purpose Pre-Processor) para outros tipos de código-fonte.

Aqui está um exemplo básico:

from pypreprocessor import pypreprocessor

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

O pré-processador é extremamente simples. Ele passa pela fonte e comenta condicionalmente a fonte com base no que está definido.

As definições podem ser definidas através de instruções #define na fonte ou definindo-as na lista pypreprocessor.defines.

A configuração dos parâmetros de entrada / saída permite definir explicitamente quais arquivos estão sendo abertos / fechados, para que um único pré-processador possa ser configurado para processar em lote um grande número de arquivos, se desejado.

Definindo o parâmetro removeMeta como True, o pré-processador deve extrair automaticamente toda e qualquer instrução de pré-processador, deixando apenas o código pós-processado.

Nota: Normalmente, isso não precisa ser definido explicitamente porque o python removeu o código comentado automaticamente durante a compilação para o bytecode.

Eu vejo apenas um caso de ponta. Como você deseja pré-processar a fonte C, convém definir explicitamente as definições do processador (por exemplo, através de pypreprocessor.defines) e pedir para ele ignorar as instruções #define na fonte. Isso deve impedir a remoção acidental de quaisquer constantes que você possa usar no código-fonte do seu projeto. Atualmente, não há parâmetro para definir essa funcionalidade, mas seria fácil adicionar isso.

Aqui está um exemplo 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()

Então a fonte:

#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

Nota: Obviamente, você precisará definir uma maneira de definir os arquivos de entrada / saída, mas isso não deve ser muito difícil.

Divulgação: Eu sou o autor original do pypreprocessor.


Além: escrevi-o originalmente como uma solução para o temido problema de manutenção em python 2k / 3x. Minha abordagem foi desenvolver 2 e 3 nos mesmos arquivos de origem e apenas incluir / excluir as diferenças usando diretivas de pré-processador. Infelizmente, descobri da maneira mais difícil que é impossível escrever um pré-processador verdadeiro puro (ou seja, não requer c) em python, porque o lexer sinaliza erros de sintaxe em código incompatível antes que o pré-processador tenha a chance de executar. De qualquer forma, ainda é útil em várias circunstâncias, incluindo a sua.

Evan Plaice
fonte
Isso é demais. Se nada mais pudéssemos fazer com o diff de três maneiras que processava os arquivos com e sem o código que queríamos excluir, pegava o diff e removia as linhas diffed do original.
DeveloperDon
@DeveloperDon Sim, essa é a ideia geral. Existem algumas maneiras diferentes de lidar com isso, depende de como você planeja gerenciar o ciclo de liberação de confirmação. Esta peça apenas automatiza grande parte do trabalho que, de outra forma, seria tedioso e / ou propenso a erros.
Evan Solha
1

Provavelmente seria uma boa ideia

1. adicione tags de comentário como:

> // *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. Escreva um script para que o construtor de código aberto analise todos os arquivos e substitua o texto entre as tags COPYRIGHT-BEGIN-TAG e COPYRIGHT-ENG-TAG

Alex Hashimi
fonte
1
Preciso da tag begin? Até agora, todos os nossos arquivos de origem começam com os direitos autorais na primeira linha e nossos scripts de shell começam com os direitos autorais na segunda linha. Existem muitos arquivos, então eu gostaria de fazer a menor quantidade possível de edição manual.
DeveloperDon
Acho que alguns arquivos podem usar o Doxygen para delinear suas funções, parâmetros e retornar nomes de valores. Para os arquivos que ainda não estão configurados dessa maneira, poderia ser realmente uma grande edição se fizéssemos uma escolha que levou mais longe nessa direção.
DeveloperDon
Pelo menos você tem que mudar uma vez. se sua política de direitos autorais foi alterada, você pode gerenciá-la.
Alex Hashimi #
1

Não vou mostrar uma ferramenta para converter sua base de código, muitas respostas já fizeram isso. Em vez disso, estou respondendo ao seu comentário sobre como lidar com ramos para isso.

Você deve ter 2 ramos:

  • Comunidade (vamos chamar a versão de código aberto assim)
  • Profissional (vamos chamar a versão de código fechado assim)

Os pré-processadores não deveriam existir. Você tem duas versões diferentes. E uma base de código mais limpa em geral.

Você tem medo de manter duas cópias em paralelo? Não se preocupe, você pode mesclar!

Se você estiver fazendo modificações no ramo da comunidade, basta mesclá-las no ramo profissional. O Git lida com isso muito bem.

Dessa forma, você mantém 2 cópias mantidas da sua base de código. E liberar um para código aberto é fácil como torta.

Florian Margaine
fonte